Fin Text Format

unstable

A general purpose notation for data interchange.

Specification Builtins Roadmap Code

Fin is a general purpose text format featuring a versatile data model, explicit extensibility and intuitive syntax for humans and machines.

Project Status

Although the core design of fin is nearing completion, the project itself is only just getting started. The current focus is on this website and the specification.

If you'd like to contribute, there are lots of interesting problems to solve. For more information check out the roadmap.

# welcome to fin notation!
example_data = [

  # symbols
  foo, _42, crypto:sha256, :key

  # numbers
  98, 1.23e-4, -42

  # strings
  "escaped \n strings"
  |raw strings are not escaped
  |and can span mutliple lines

  # collections (arrays & maps)
  [], tagged["array"], [98, goal, []]
  (), (key = "val", 55 = tagged:map())

  # extensions
  true, fin:timestamp["2024-02-26T03:01:13.644Z"]
  ext:null, ext:nan, ext:loc(x = 1, y = 2)
]

Versatile Data Model

Fin has simple, self-describing, value semantics (like JSON). It's not concerned with higher level concepts such as schemas, validation, objects, references or RPC.

The data model has a small set of core value types (symbols, strings, numbers, arrays and maps) which are combined to form a top level value.

The collection types (arrays and maps) are especially versatile, supporting elements of any type. They can also be tagged with a leading symbol. Tagged collections are a natural fit for describing programs and DSLs.

# frontmatter
title = "Versatile Data Model"
tags = [fin, data]
draft = false
pub_date = fin:timestamp["2024-02-26T03:01:13.644Z"]

# represent html
html_fragment = div[
  (class = "prose", id = "hero")
  h1["Fin Data Format"]
  p["A notation for data interchange"], hr
  img(
    # example with no children
    src = "fin.webp", alt = "fin data notation"
    width = 512, height = 512
  )
]

# describe a program
crypto = :namespace(url = "https://blah/crypto")
in = fin:bytes[""]
out = fin:bytes["e3b0c44298fc1c149afbf4..."]
main = :function(
  body = [
    # sha256 test vector
    :assert_eq[crypto:sha256[in], out]
  ]
)

Explicit Extensibility

Fin dedicates part of its syntax (a subset of symbols and tagged collections) for extension. Symbol prefixes clearly mark extension points where programmers can register and run custom logic.

Clear extensibility is nice, but putting the burden on developers to write extensions for every value type outside of fins data model is a recipe for error. Especially for universal types where external parties don't have a connection when they really should.

Fin addresses this with builtins - a curated set of extensions that leverage existing standards. Compliant fin libraries support builtins out of the box. The hope is, in most cases, developers won't need to reach for extensions.

extensions = [

  # builtin extensions (start with fin:)
  fin:timestamp["2024-02-26T03:01:13.644Z"]
  fin:bytes[
    |f6 69 6e 20 54 d6 78 74
    |2a 46 6f 72 6d 61 74 0a
  ]
  fin:uuid["c83ccbb6-44d0-4d62-8263-b8afec01f3a2"]

  # booleans are builtins
  true, false

  # extensions ending with _ are discarded
  fin:_(is_value = false, treated_as = "comment")

  # custom extensions (start with ext:)
  ext:null, ext:nan, ext:int:hex_10fe
  ext:loc(x = 1, y = 2) 
  ext:markdown[
    |# title 
    |
    |a **markdown** extension!
  ]
]

Intuitive Syntax

Data interchange between programs doesn’t require comments, commas, newlines or indentation to correctly convey values. However, fin is designed for us too, so it does have syntax elements for humans.

In fin these elements directly control the (auto-formatted) output in an obvious, canonical way. Machines generate mostly inline data, while human authored notation will expand as required with zero configuration.

# symbols, numbers & escaped strings are INLINE
inline = "contains no newlines (LF)"

# comments and raw strings are EXPANDED
expanded = 
| they require a newline to terminate

# collections expand if any child has expanded
expanded_array = [
  # this comment expands the array!
  # - nested elements are indented on each line
  # - consecutive empty lines are removed
  # - comma signals multiple elements per line
  1, 2
]

ex_map = (
  in_arr = [all, elements, "are inline"]
  ex_arr = [
    | this raw string forces ex_arr
    | and ex_map to expand!
  ]
)

Robust Tools

Simple specifications are great, but when human producers and consumers are involved they can lead to bad ergonomics and even vulnerabilities.

Prior to a stable release of the specification, the fin project aims to offer an official CLI with tools to provide editor diagnostics and other quality of life features for working with fin data.


isUser = false

● invalid uppercase. Convert to is_user?


#  admins only  

● bidirectional control code detected. Remove?
● non-standard line ending. Convert to LF?