Skip to content

Instantly share code, notes, and snippets.

@Guria
Last active April 19, 2021 21:14
Show Gist options
  • Save Guria/4be1d386421cbf178b7b570553f92648 to your computer and use it in GitHub Desktop.
Save Guria/4be1d386421cbf178b7b570553f92648 to your computer and use it in GitHub Desktop.
%%private(let keyStringifyRegexp = %re("/([=:@$/])/g"))
%%private(let valueStringifyRegexp = %re("/([&;/])/g"))
%%private(let keyParseRegexp = %re("/[=:@$]/"))
%%private(let valueParseRegexp = %re("/[&;]/"))
%%private(
let encodeString = (str, regexp) =>
Js.Global.encodeURI(Js.String2.replaceByRe(str, regexp, "/$1"))
)
%%private(
let trim = (maybeRes: option<string>) =>
Js.Option.map((. res: string) => Js.String2.replaceByRe(res, %re("/;+$/g"), ""), maybeRes)
)
type matcher = {maybeInput: option<Js.Json.t>, recursive: bool}
let rec stringify = (maybeInput: option<Js.Json.t>, ~recursive: bool=false, ()): option<string> => {
let match = {maybeInput: maybeInput, recursive: recursive}
switch match {
| {maybeInput: None} => None
| {recursive: false} => trim(stringify(maybeInput, ~recursive=true, ()))
| {maybeInput: Some(input)} =>
switch Js.Json.classify(input) {
| Js.Json.JSONArray(arr) =>
Js.Array2.map(arr, x => {
switch Js.Nullable.toOption(Js.Nullable.return(x)) {
| None => Some(":null")
| Some(_) => stringify(Some(x), ~recursive=true, ())
}
})
->Js.Array2.joinWith("&")
->(str => Some("@" ++ str ++ ";"))
| Js.Json.JSONObject(obj) => {
let arr: Js.Array2.t<string> = []
Js.Array2.forEach(Js.Dict.entries(obj), ((key, value)) => {
if Js.Nullable.return(value) != Js.Nullable.undefined {
switch stringify(Some(value), ~recursive=true, ()) {
| None => ()
| Some(val) =>
(Js.Array2.push(arr, encodeString(key, keyStringifyRegexp) ++ val): int)->ignore
}
}
})
arr->Js.Array2.joinWith("&")->(str => Some("$" ++ str ++ ";"))
}
| Js.Json.JSONString(str) => Some("=" ++ encodeString(str, valueStringifyRegexp))
| Js.Json.JSONNumber(_)
| Js.Json.JSONTrue
| Js.Json.JSONFalse
| Js.Json.JSONNull =>
Some(j`:$input`)
}
}
}
let parse = (input: string): Js.Json.t => {
let pos = ref(0)
let str = Js.Global.decodeURI(input)
let readChar = () => Js.String2.charAt(str, pos.contents)
let seek = () => pos.contents = pos.contents + 1
let isReachedEnd = () => pos.contents >= Js.String2.length(str)
let isReachedSeparator = () => isReachedEnd() || readChar() == ";"
let readToken = (regexp: Js_re.t): string => {
let token = ref("")
let break = ref(false)
while !break.contents {
switch readChar() {
| "/" => {
seek()
if isReachedEnd() {
token.contents = token.contents ++ ";"
break.contents = true
}
}
| char =>
if Js.Re.test_(regexp, char) {
break.contents = true
}
}
if !break.contents {
token.contents = token.contents ++ readChar()
seek()
}
if isReachedEnd() {
break.contents = true
}
}
token.contents
}
let rec parseToken = (): Js.Json.t => {
let tokenType = readChar()
seek()
switch tokenType {
| "=" => Js.Json.string(readToken(valueParseRegexp))
| ":" =>
switch readToken(valueParseRegexp) {
| "true" => Js.Json.boolean(true)
| "false" => Js.Json.boolean(false)
| token => {
let value = Js.Float.fromString(token)
Js.Float.isNaN(value) ? Js.Json.null : Js.Json.number(value)
}
}
| "@" => {
let res: Js.Array2.t<Js.Json.t> = []
let break = ref(false)
if isReachedSeparator() {
break.contents = true
}
while !break.contents {
(Js.Array2.push(res, parseToken()): int)->ignore
if isReachedSeparator() {
break.contents = true
}
if !break.contents {
seek()
}
}
seek()
Js.Json.array(res)
}
| "$" => {
let res: Js.Dict.t<Js.Json.t> = Js.Dict.empty()
let break = ref(false)
if isReachedSeparator() {
break.contents = true
}
while !break.contents {
let name = readToken(keyParseRegexp)
Js.Dict.set(res, name, parseToken())
if isReachedSeparator() {
break.contents = true
}
if !break.contents {
seek()
}
}
seek()
Js.Json.object_(res)
}
| _ => Js.Exn.raiseError("Unknown type: " ++ tokenType)
}
}
parseToken()
}
@Guria
Copy link
Author

Guria commented Apr 19, 2021

compiled result (formatted with prettier):

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict'

var Js_exn = require('bs-platform/lib/js/js_exn.js')
var Js_dict = require('bs-platform/lib/js/js_dict.js')
var Js_json = require('bs-platform/lib/js/js_json.js')
var Js_option = require('bs-platform/lib/js/js_option.js')
var Caml_option = require('bs-platform/lib/js/caml_option.js')

var keyStringifyRegexp = /([=:@$/])/g

var valueStringifyRegexp = /([&;/])/g

var keyParseRegexp = /[=:@$]/

var valueParseRegexp = /[&;]/

function encodeString(str, regexp) {
  return encodeURI(str.replace(regexp, '/$1'))
}

function stringify(maybeInput, recursiveOpt, param) {
  var recursive = recursiveOpt !== undefined ? recursiveOpt : false
  if (maybeInput === undefined) {
    return
  }
  if (!recursive) {
    var maybeRes = stringify(maybeInput, true, undefined)
    return Js_option.map(function (res) {
      return res.replace(/;+$/g, '')
    }, maybeRes)
  }
  var input = Caml_option.valFromOption(maybeInput)
  var arr = Js_json.classify(input)
  if (typeof arr === 'number') {
    return ':' + input
  }
  switch (arr.TAG | 0) {
    case /* JSONString */ 0:
      return '=' + encodeString(arr._0, valueStringifyRegexp)
    case /* JSONObject */ 2:
      var arr$1 = []
      Js_dict.entries(arr._0).forEach(function (param) {
        var value = param[1]
        if (value === undefined) {
          return
        }
        var val = stringify(Caml_option.some(value), true, undefined)
        if (val !== undefined) {
          arr$1.push(encodeString(param[0], keyStringifyRegexp) + val)
        }
      })
      var str = arr$1.join('&')
      return '$' + str + ';'
    case /* JSONArray */ 3:
      var str$1 = arr._0
        .map(function (x) {
          if (x == null) {
            return ':null'
          } else {
            return stringify(Caml_option.some(x), true, undefined)
          }
        })
        .join('&')
      return '@' + str$1 + ';'
    default:
      return ':' + input
  }
}

function parse(input) {
  var pos = {
    contents: 0,
  }
  var str = decodeURI(input)
  var seek = function (param) {
    pos.contents = (pos.contents + 1) | 0
  }
  var isReachedEnd = function (param) {
    return pos.contents >= str.length
  }
  var isReachedSeparator = function (param) {
    if (isReachedEnd(undefined)) {
      return true
    } else {
      return str.charAt(pos.contents) === ';'
    }
  }
  var readToken = function (regexp) {
    var token = ''
    var $$break = false
    while (!$$break) {
      var $$char = str.charAt(pos.contents)
      if ($$char === '/') {
        seek(undefined)
        if (isReachedEnd(undefined)) {
          token = token + ';'
          $$break = true
        }
      } else if (regexp.test($$char)) {
        $$break = true
      }
      if (!$$break) {
        token = token + str.charAt(pos.contents)
        seek(undefined)
      }
      if (isReachedEnd(undefined)) {
        $$break = true
      }
    }
    return token
  }
  var parseToken = function (param) {
    var tokenType = str.charAt(pos.contents)
    seek(undefined)
    switch (tokenType) {
      case '$':
        var res = {}
        var $$break = false
        if (isReachedSeparator(undefined)) {
          $$break = true
        }
        while (!$$break) {
          var name = readToken(keyParseRegexp)
          res[name] = parseToken(undefined)
          if (isReachedSeparator(undefined)) {
            $$break = true
          }
          if (!$$break) {
            seek(undefined)
          }
        }
        seek(undefined)
        return res
      case ':':
        var token = readToken(valueParseRegexp)
        switch (token) {
          case 'false':
            return false
          case 'true':
            return true
          default:
            var value = Number(token)
            if (isNaN(value)) {
              return null
            } else {
              return value
            }
        }
      case '=':
        return readToken(valueParseRegexp)
      case '@':
        var res$1 = []
        var $$break$1 = false
        if (isReachedSeparator(undefined)) {
          $$break$1 = true
        }
        while (!$$break$1) {
          res$1.push(parseToken(undefined))
          if (isReachedSeparator(undefined)) {
            $$break$1 = true
          }
          if (!$$break$1) {
            seek(undefined)
          }
        }
        seek(undefined)
        return res$1
      default:
        return Js_exn.raiseError('Unknown type: ' + tokenType)
    }
  }
  return parseToken(undefined)
}

exports.stringify = stringify
exports.parse = parse
/* No side effect */

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment