# General

hurl-file: entry*
           lt*

entry: request
       response?

request:
  lt*
  method sp value-string lt
  header*
  request-section*
  body?

response:
  lt*
  version sp status lt
  header*
  response-section*
  body?

method: [A-Z]+

version:
    "HTTP/1.0"
  | "HTTP/1.1"
  | "HTTP/2"
  | "HTTP"

status: [0-9]+

header:
  lt*
  key-value lt

body:
  lt*
  bytes lt


# Sections

request-section:
    basic-auth-section
  | query-string-params-section
  | form-params-section
  | multipart-form-data-section
  | cookies-section
  | options-section

response-section:
    captures-section
  | asserts-section

query-string-params-section:
  lt*
  ("[QueryStringParams]" | "[Query]") lt
  key-value*

form-params-section:
  lt*
  ("[FormParams]" | "[Form]") lt
  key-value*

multipart-form-data-section:
  lt*
  ("[MultipartFormData]" | "[Multipart]") lt
  multipart-form-data-param*

cookies-section:
  lt*
  "[Cookies]" lt
  key-value*

captures-section:
  lt*
  "[Captures]" lt
  capture*

asserts-section:
  lt*
  "[Asserts]" lt
  assert*

basic-auth-section:
  lt*
  "[BasicAuth]" lt
  key-value*


options-section:
  lt*
  "[Options]" lt
  option*

key-value: key-string ":" value-string

multipart-form-data-param: file-param | key-value

file-param :
  lt*
  key-string ":" file-value lt

file-value: "file," filename ";" (file-contenttype)?

file-contenttype: [a-zA-Z0-9/+-]+

capture:
  lt*
  key-string ":" query (sp filter)* lt

assert:
  lt*
  query (sp filter)* sp predicate lt

option:
  lt*
  ( aws-sigv4-option
  | ca-certificate-option
  | client-certificate-option
  | client-key-option
  | compressed-option
  | connect-to-option
  | connect-timeout-option
  | delay-option
  | follow-redirect-option
  | follow-redirect-trusted-option
  | http10-option
  | http11-option
  | http2-option
  | http3-option
  | insecure-option
  | ipv4-option
  | ipv6-option
  | limit-rate-option
  | max-redirs-option
  | netrc-option
  | netrc-file-option
  | netrc-optional-option
  | output-option
  | path-as-is-option
  | proxy-option
  | repeat-option
  | resolve-option
  | retry-option
  | retry-interval-option
  | skip-option
  | unix-socket-option
  | user-option
  | variable-option
  | verbose-option
  | very-verbose-option
  )

aws-sigv4-option: "aws-sigv4" ":" value-string lt

ca-certificate-option: "cacert" ":" filename lt

client-certificate-option: "cert" ":" filename-password lt

client-key-option: "key" ":" value-string lt

compressed-option: "compressed" ":" boolean-option lt

connect-to-option: "connect-to" ":" value-string lt

connect-timeout-option: "connect-timeout" ":" duration-option lt

delay-option: "delay" ":" duration-option lt

follow-redirect-option: "location" ":" boolean-option lt

follow-redirect-trusted-option: "location-trusted" ":" boolean-option lt

http10-option: "http1.0" ":" boolean-option lt

http11-option: "http1.1" ":" boolean-option lt

http2-option: "http2" ":" boolean-option lt

http3-option: "http3" ":" boolean-option lt

insecure-option: "insecure" ":" boolean-option lt

ipv4-option: "ipv4" ":" boolean-option lt

ipv6-option: "ipv6" ":" boolean-option lt

limit-rate-option: "limit-rate" ":" integer-option lt

max-redirs-option: "max-redirs" ":" integer-option lt

netrc-option: "netrc" ":" boolean-option lt

netrc-file-option: "netrc-file" ":" value-string lt

netrc-optional-option: "netrc-optional" ":" boolean-option lt

output-option: "output" ":" value-string lt

path-as-is-option: "path-as-is" ":" boolean-option lt

proxy-option: "proxy" ":" value-string lt

resolve-option: "resolve" ":" value-string lt

repeat-option: "repeat" ":" integer-option lt

retry-option: "retry" ":" integer-option lt

retry-interval-option: "retry-interval" ":" duration-option lt

skip-option: "skip" ":" boolean-option lt

unix-socket-option: "unix-socket" ":" value-string lt

user-option: "user" ":" value-string lt

variable-option: "variable" ":" variable-definition lt

verbose-option: "verbose" ":" boolean-option lt

very-verbose-option: "very-verbose" ":" boolean-option lt

variable-definition: variable-name "=" variable-value

boolean-option: boolean | placeholder

integer-option: integer | placeholder

duration-option: (integer duration-unit?)  | placeholder

duration-unit: "ms" | "s" | "m"

variable-value:
    null
  | boolean
  | integer
  | float
  | key-string
  | quoted-string

# Query

query:
    status-query
  | url-query
  | header-query
  | certificate-query
  | cookie-query
  | body-query
  | xpath-query
  | jsonpath-query
  | regex-query
  | variable-query
  | duration-query
  | bytes-query
  | sha256-query
  | md5-query

status-query: "status"

url-query: "url"

header-query: "header" sp quoted-string

certificate-query: "certificate" sp ("Subject" | "Issuer" | "Start-Date" | "Expire-Date" | "Serial-Number")

cookie-query: "cookie" sp quoted-string

body-query: "body"

xpath-query: "xpath" sp quoted-string

jsonpath-query: "jsonpath" sp quoted-string

regex-query: "regex" sp (quoted-string | regex)

variable-query: "variable" sp quoted-string

duration-query: "duration"

sha256-query: "sha256"

md5-query: "md5"

bytes-query: "bytes"


# Predicates

predicate: ("not" sp )? predicate-func

predicate-func:
    equal-predicate
  | not-equal-predicate
  | greater-predicate
  | greater-or-equal-predicate
  | less-predicate
  | less-or-equal-predicate
  | start-with-predicate
  | end-with-predicate
  | contain-predicate
  | match-predicate
  | exist-predicate
  | is-empty-predicate
  | include-predicate
  | integer-predicate
  | float-predicate
  | boolean-predicate
  | string-predicate
  | collection-predicate
  | date-predicate
  | iso-date-predicate

equal-predicate: "==" sp predicate-value

not-equal-predicate: "!=" sp predicate-value

greater-predicate: ">" sp (number | quoted-string)

greater-or-equal-predicate: ">=" sp sp* (number | quoted-string)

less-predicate: "<" sp (number | quoted-string)

less-or-equal-predicate: "<=" sp (number | quoted-string)

start-with-predicate: "startsWith" sp (quoted-string | oneline-hex | oneline-base64)

end-with-predicate: "endsWith" sp (quoted-string | oneline-hex | oneline-base64)

contain-predicate: "contains" sp quoted-string

match-predicate: "matches" sp (quoted-string | regex)

exist-predicate: "exists"

is-empty-predicate: "isEmpty"

include-predicate: "includes" sp predicate-value

integer-predicate: "isInteger"

float-predicate: "isFloat"

boolean-predicate: "isBoolean"

string-predicate: "isString"

collection-predicate: "isCollection"

date-predicate: "isDate"

iso-date-predicate: "isIsoDate"

predicate-value:
    boolean
  | multiline-string
  | null
  | number
  | oneline-string
  | oneline-base64
  | oneline-file
  | oneline-hex
  | quoted-string
  | placeholder


# Bytes

bytes:
    json-value
  | xml
  | multiline-string
  | oneline-string
  | oneline-base64
  | oneline-file
  | oneline-hex

xml: "<" "To Be Defined" ">"

oneline-base64: "base64," [A-Z0-9+-= \n]+ ";"

oneline-file: "file," filename ";"

oneline-hex: "hex," hexdigit* ";"




# Strings

quoted-string: "\"" (quoted-string-content | placeholder)* "\""

quoted-string-content: (quoted-string-text | quoted-string-escaped-char)*

quoted-string-text: ~["\\]+

quoted-string-escaped-char: "\\" ("\"" | "\\" | "\b" | "\f" | "\n" | "\r" | "\t" | "\u" unicode-char)


key-string: (key-string-content | placeholder)+

key-string-content: (key-string-text | key-string-escaped-char)*

key-string-text: (alphanum | "_" | "-" | "." | "[" | "]" | "@" | "$") +

key-string-escaped-char: "\\" ("#" | ":" | "\\" | "\b" | "\f" | "\n" | "\r" | "\t" | "\u" unicode-char )


value-string: (value-string-content | placeholder)*

value-string-content: (value-string-text | value-string-escaped-char)*

value-string-text: ~[#\n\\]+

value-string-escaped-char: "\\" ("#" | "\\" | "\b" | "\f" | "\n" | "\r" | "\t" | "\u" unicode-char )


oneline-string: "`" (oneline-string-content | placeholder)* "`"

oneline-string-content: (oneline-string-text | oneline-string-escaped-char)*

oneline-string-text: ~[#\n\\] ~"`"

oneline-string-escaped-char: "\\" ("`" | "#" | "\\" | "b" | "f" | "u" unicode-char )


multiline-string:
  "```" multiline-string-type? ("," multiline-string-attribute)* lt
  (multiline-string-content | placeholder)* lt
  "```"

multiline-string-type:
    "base64"
  | "hex"
  | "json"
  | "xml"
  | "graphql"

multiline-string-attribute:
    "escape"
  | "novariable"

multiline-string-content: (multiline-string-text | multiline-string-escaped-char)*

multiline-string-text:  ~[\\]+ ~"```"

multiline-string-escaped-char: "\\" ( "\\" | "b" | "f" | "n" | "r" | "t" | "`" | "u" unicode-char)

filename: (filename-content | placeholder)*

filename-content: (filename-text | filename-escaped-char)*

filename-text: ~[#;{} \n\\]+

filename-escaped-char: "\\" ( "\\" | "b" | "f" | "n" | "r" | "t" | "#" | ";"| " " | "{" | "}" | "u" unicode-char)


filename-password: (filename-password-content | placeholder)*

filename-password-content: (filename-password-text | filename-password-escaped-char)*

filename-password-text: ~[#;{} \n\\]+

filename-password-escaped-char: "\\" ( "\\" | "b" | "f" | "n" | "r" | "t" | "#" | ";" | " " | "{" | "}" | ":" | "u" unicode-char)

unicode-char: "{" hexdigit+ "}"




# JSON

json-value:
    placeholder
  | json-object
  | json-array
  | json-string
  | json-number
  | boolean
  | null

json-object: "{" json-key-value ("," json-key-value)* "}"

json-key-value: json-string ":" json-value

json-array: "[" json-value ("," json-value)* "]"

json-string: "\"" (json-string-content | placeholder)* "\""

json-string-content: json-string-text | json-string-escaped-char

json-string-text: ~["\\]

json-string-escaped-char:
    "\\" ("\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "u" hexdigit hexdigit hexdigit hexdigit)

json-number: integer fraction? exponent?


#  Expression

placeholder: "{{" expr "}}"

expr: (variable-name | function) (sp filter)*

variable-name: [A-Za-z] [A-Za-z_-0-9]*


# Function

function:
    env-function
  | now-function
  | uuid-function

env-function: "getEnv"

now-function: "newDate"

uuid-function: "newUuid"



# Filter

filter:
    count-filter
  | days-after-now-filter
  | days-before-now-filter
  | decode-filter
  | format-filter
  | html-escape-filter
  | html-unescape-filter
  | jsonpath-filter
  | nth-filter
  | regex-filter
  | replace-filter
  | split-filter
  | to-date-filter
  | to-float-filter
  | to-int-filter
  | url-decode-filter
  | url-encode-filter
  | xpath-filter

count-filter: "count"

days-after-now-filter: "daysAfterNow"

days-before-now-filter: "daysBeforeNow"

decode-filter: "decode"

format-filter: "format"

html-escape-filter: "htmlEscape"

html-unescape-filter: "htmlUnescape"

jsonpath-filter: "jsonpath" sp quoted-string

nth-filter: "nth" sp integer

regex-filter: "regex" sp (quoted-string | regex)

replace-filter: "replace" sp (quoted-string | regex) sp quoted-string

split-filter: "split" sp quoted-string

to-date-filter: "toDate"

to-float-filter: "toFloat"

to-int-filter: "toInt"

url-decode-filter: "urlDecode"

url-encode-filter: "urlEncode"

xpath-filter: "xpath" sp quoted-string


# Lexical Grammar

boolean: "true" | "false"

null: "null"

alphanum: [A-Za-z0-9]

integer: digit+

float: integer fraction

number: integer | float

digit: [0-9]

hexdigit: [0-9A-Fa-f]

fraction: "." digit+

exponent: ("e" | "E") ("+"|"-")? digit+

sp: [ \t]

lt: sp* comment? [\n]?

comment: "#" ~[\n]*

regex: "/" regex-content "/"

regex-content: (regex-text|regex-escaped-char)*

regex-text: ~[\n\/]+

regex-escaped-char: "\\" ~[\n]
