Skip to content
/ QCL Public

An experimental configuration language that produces JSON

Notifications You must be signed in to change notification settings

kccqzy/QCL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

QCL is an experimental, dynamically typed "little language" (as defined by Chapter 6 of The AWK Programming Language) that produces JSON output. It is JSON but more convenient to write, and it allows a little bit of computation.

Motivation

This project mainly exists because I want to play with a natural and intuitive (to me) syntax. I also want to play with the Earley parser which is capable of parsing context-free languages (I want my language to be strictly context-free after lexing). I also want to play with making named tuples (aka dictionaries, JS-style objects) a first-class citizen of the language, such that almost everything is built on top of it. The abstract tuple is a powerful feature whereby the evaluation of the tuple is delayed until such time intended by the user through the eval keyword. It can be thought of as providing a template tuple with values to be filled in later; it can also be thought of as introducing a lambda function where the values to be filled in are function arguments. These lambdas are closures and capture their environment.

I also want to produce extremely helpful and detailed error messages, which sadly I usually don't get to do too often.

Don't use this in production. This is my playground.

Demo

This shows real examples. Feel free to run it using the configlang-examples executable.

QCL:

true

JSON result:

true

QCL:

1.234

JSON result:

1.234

QCL:

1+2+3+4+5

JSON result:

15

QCL:

17.5 / 5.5

JSON result:

3.1818181818181817

QCL:

17.5 // 5.5

JSON result:

3

QCL:

17.5 % 5.5

JSON result:

1

QCL:

1 + 2 * 3 + 4 < 5

JSON result:

false

QCL:

a+b.c.d

Error message:

error:
    variable reference "a" must be inside a tuple
  |
1 | a+b.c.d
  | ^ top-level variable reference


QCL:

{}

JSON result:

{}

QCL:

{} =

Error message:

parse error:
  |
1 | {} =
  |    ^ expecting "||", "&&", "!=", "==", ">=", ">", "<=", "<", "-", "+", "//", "%", "/", "*", ".", "{", found "="


QCL:

{} { }

JSON result:

{}

QCL:

{x = true}

JSON result:

{"x":true}

QCL:

{x = true, }

JSON result:

{"x":true}

QCL:

{x = true,
 y = false
}

JSON result:

{"x":true,"y":false}

QCL:

{x = true,
 y = false,
}

JSON result:

{"x":true,"y":false}

QCL:

{x = true,
 y = x + 1}

JSON result:

{"x":true,"y":2}

QCL:

{x = true,
 y }

Error message:

parse error:
  |
2 |  y }
  |    ^ expecting "%=", "//=", "/=", "*=", "-=", "+=", "{", "=", found "}"


QCL:

{private x = true,
 y = x + 1}

JSON result:

{"y":2}

QCL:

{private x = true, y = x + 1} { x = false }

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | {private x = true, y = x + 1} { x = false }
  |                                 ^ access of private field here
  |
1 | {private x = true, y = x + 1} { x = false }
  |          ^ defined here
  |
1 | {private x = true, y = x + 1} { x = false }
  |  ^^^^^^^ marked as private here


QCL:

{private x = true, y = x + 1} { delete x }

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | {private x = true, y = x + 1} { delete x }
  |                                        ^ access of private field here
  |
1 | {private x = true, y = x + 1} { delete x }
  |          ^ defined here
  |
1 | {private x = true, y = x + 1} { delete x }
  |  ^^^^^^^ marked as private here


QCL:

{private x = true, y = x + 1}.x

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | {private x = true, y = x + 1}.x
  |                               ^ access of private field here
  |
1 | {private x = true, y = x + 1}.x
  |          ^ defined here
  |
1 | {private x = true, y = x + 1}.x
  |  ^^^^^^^ marked as private here


QCL:

{private x = true} { private x = false}

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | {private x = true} { private x = false}
  |                              ^ access of private field here
  |
1 | {private x = true} { private x = false}
  |          ^ defined here
  |
1 | {private x = true} { private x = false}
  |  ^^^^^^^ marked as private here


QCL:

{x = true,
 assert(true), }

JSON result:

{"x":true}

QCL:

{x = 5, assert(x % 2
==
0), }

Error message:

error:
    assertion failed
  |
1 | {x = 5, assert(x % 2
  |                ^^^^^
2 | ==
  | ^^
3 | 0), }
  | ^ evaluates to false
  |
1 | {x = 5, assert(x % 2
  |                ^ this has value 5


QCL:

{x = 2,
 x = 1}

Error message:

error:
    duplicate field label "x" in tuple
  |
2 |  x = 1}
  |  ^ this definition
  |
1 | {x = 2,
  |  ^ earlier definition


QCL:

1 + { a = 2, b = a + 3}.b

JSON result:

6

QCL:

1 + { a = 2, b = a + 3}

Error message:

error:
    unexpected type for expression
  |
1 | 1 + { a = 2, b = a + 3}
  |     ^^^^^^^^^^^^^^^^^^^ expecting number or boolean, found tuple


QCL:

{
 a = 1,
 b = a + a,
 c = a + b + c
}.c

Error message:

error:
    variable reference "c" does not exist
  |
4 |  c = a + b + c
  |              ^ undefined variable reference


QCL:

[]

JSON result:

[]

QCL:

[1, true]

JSON result:

[1,true]

QCL:

{a = {b = {c=1}}, ret = a.b{x=1}}.ret

JSON result:

{"c":1,"x":1}

QCL:

{a = {b = {c=1}}, ret = a{x=1}.b}.ret

JSON result:

{"c":1}

QCL:

{ x=1, y=2, z=3 } {z = 4}

JSON result:

{"x":1,"y":2,"z":4}

QCL:

{ x=1, y=2, z=3 } {z += 4}

JSON result:

{"x":1,"y":2,"z":7}

QCL:

{ x=1, y=2, z=3 } {z *= z}

JSON result:

{"x":1,"y":2,"z":9}

QCL:

{ x=1, y=2, z=3 } {delete z}

JSON result:

{"x":1,"y":2}

QCL:

{ x=1, y=2, z=3 } .x

JSON result:

1

QCL:

{ x=1, y=2, z=3 }.wwww

Error message:

error:
    label "wwww" does not exist in tuple
  |
1 | { x=1, y=2, z=3 }.wwww
  |                   ^^^^ tuple has labels "x", "y", "z"


QCL:

{ x=1, y=2, z=y }

JSON result:

{"x":1,"y":2,"z":2}

QCL:

{ x=1, y=2, z={a=1, b=y} }

JSON result:

{"x":1,"y":2,"z":{"a":1,"b":2}}

QCL:

{ x=1, y=2, z={a=1, b=a} }

JSON result:

{"x":1,"y":2,"z":{"a":1,"b":1}}

QCL:

{ x=1, y=2, z={x=1, y=x} }

Error message:

error:
    variable reference "x" is ambiguous
  |
1 | { x=1, y=2, z={x=1, y=x} }
  |                       ^ variable reference used here
  |
1 | { x=1, y=2, z={x=1, y=x} }
  |                ^ possible referent
  |
1 | { x=1, y=2, z={x=1, y=x} }
  |   ^ another possible referent


QCL:

{ inner = { private x = 1, y = 2 }, result = inner.x }

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | { inner = { private x = 1, y = 2 }, result = inner.x }
  |                                                    ^ access of private field here
  |
1 | { inner = { private x = 1, y = 2 }, result = inner.x }
  |                     ^ defined here
  |
1 | { inner = { private x = 1, y = 2 }, result = inner.x }
  |             ^^^^^^^ marked as private here


QCL:

{ private x = 1, inner = { y = 2 + x } }

JSON result:

{"inner":{"y":3}}

QCL:

# A convenient syntax for nested tuple updates.
{ a=1, b={ x=2, y=3 } } { b { y = 100 } }

JSON result:

{"a":1,"b":{"x":2,"y":100}}

QCL:

{ z = { irrelevant = 1000 },
  inner = {
    z = { x = 0 }
  } {
    z { x += 1 } # This z updates the field in the current tuple, not the outside one.
  }
}

JSON result:

{"inner":{"z":{"x":1}},"z":{"irrelevant":1000}}

QCL:

{ x = 1, y = 2, z = { a = x + 1, b = y + a + 2}}.z.b

JSON result:

6

QCL:

{ a=1, b=2 } { a=b+1 } { b=a+1 } { a=b+1 } { b=a+1 }

JSON result:

{"a":5,"b":6}

QCL:

{ final meaningOfLife = 42 } { meaningOfLife = 43 }

Error message:

error:
    field marked as final cannot be overridden
  |
1 | { final meaningOfLife = 42 } { meaningOfLife = 43 }
  |                                ^^^^^^^^^^^^^ override here
  |
1 | { final meaningOfLife = 42 } { meaningOfLife = 43 }
  |         ^^^^^^^^^^^^^ defined here
  |
1 | { final meaningOfLife = 42 } { meaningOfLife = 43 }
  |   ^^^^^ marked as final here


QCL:

{ final
  meaningOfLife = 42 } { delete meaningOfLife }

Error message:

error:
    field marked as final cannot be overridden
  |
2 |   meaningOfLife = 42 } { delete meaningOfLife }
  |                                 ^^^^^^^^^^^^^ override here
  |
2 |   meaningOfLife = 42 } { delete meaningOfLife }
  |   ^^^^^^^^^^^^^ defined here
  |
1 | { final
  |   ^^^^^ marked as final here


QCL:

{
  # Perl-style boolean operators.
  oneOrTwo    = 1 || 2,
  zeroOrTwo   = 0 || 2,
  oneAndTwo   = 1 && 2,
  zeroAndTwo  = 0 && 2,
  # Substitute for the conditional operator.
  ifTrue      = 1 && 100 || 200,
  ifFalse     = 0 && 100 || 200,
  # Short-circuiting. Even type errors are not found.
  simplyFalse = false && (1+{}),
  simplyTrue  = true  || (1+{}),
}

JSON result:

{"ifFalse":200,"ifTrue":100,"oneAndTwo":2,"oneOrTwo":1,"simplyFalse":false,"simplyTrue":true,"zeroAndTwo":0,"zeroOrTwo":2}

QCL:

{ abstract a }

Error message:

error:
    abstract field cannot be used in non-abstract tuples
  |
1 | { abstract a }
  |   ^^^^^^^^ abstract field


QCL:

{}.eval

Error message:

error:
    unexpected type for expression
  |
1 | {}.eval
  | ^^ expecting abstract tuple, found tuple


QCL:

# Abstract tuples are not automatically evaluated. It is not evaluated here.
abstract {
  abstract a,
  assert (a % 2 == 0),
  ret = a / 2,
}

JSON result:

null

QCL:

# Abstract tuple updates are also not automatically evaluated. It is not evaluated here.
abstract {
  abstract a,
  assert (a % 2 == 0),
  ret = a / 2,
} { a = 42 }

JSON result:

null

QCL:

# The abstract tuple must be evaluated explicitly using the eval keyword.
abstract {
  abstract a,
  assert (a % 2 == 0),
  ret = a / 2,
} { a = 42 }.eval

JSON result:

{"a":42,"ret":21}

QCL:

{
  private checkEven = abstract {
    abstract a,
    assert(a % 2 == 0),
    ret = a / 2,
  },
  e1 = checkEven { a = 100 },
} { e1 = e1.eval.ret }

JSON result:

{"e1":50}

QCL:

{
  private checkEven = abstract {
    abstract a,
    # This will fail.
    assert(a % 2 == 0),
    ret = a / 2,
  },
  e1 = checkEven { a = 105 },
} { e1 = e1.eval.ret }

Error message:

error:
    assertion failed
  |
5 |     assert(a % 2 == 0),
  |            ^^^^^^^^^^ evaluates to false
  |
5 |     assert(a % 2 == 0),
  |            ^ this has value 105


QCL:

abstract {
  abstract a,
  assert (a % 2 == 0),
  ret = a / 2,
} {
  a = 42
} {
  a = 64 # This override fails because abstract rows must be overridden exactly once.
}.eval

Error message:

error:
    non-abstract field cannot be overridden in an abstract tuple
        (consider first evaluating the abstract tuple into a tuple,
        and then overriding this field)
  |
8 |   a = 64 # This override fails because abstract rows must be overridden exactly once.
  |   ^ override of field here
  |
6 |   a = 42
  |   ^ previous value defined here
  |
1 | abstract {
  | ^^^^^^^^ tuple marked as abstract here


QCL:

# Variables in abstract tuples can refer to the surrounding scope (lexical scope).
{ a = 1,
  b = abstract {
    c = a
  }.eval,
  assert (b.c==a)
}

JSON result:

{"a":1,"b":{"c":1}}

QCL:

# Variables in abstract tuple updates can also refer to the surrounding scope.
{ a = 1,
  b = abstract {
    abstract c
  }
} {
  b = b {c = a}.eval,
  assert (b.c==a)
}

JSON result:

{"a":1,"b":{"c":1}}

QCL:

# Variables in abstract tuple updates refer to the value upon the update, not during evaluation.
{ a = 1,
  b = abstract {
    abstract c
  }
} {
  b {c = a}, # This `a` is 1.
  a = a + a, # `a` becomes 2
  b = b.eval, # Evaluation of `b` uses the `a` whose value is 1.
  assert (b.c!=a)
}

JSON result:

{"a":2,"b":{"c":1}}

QCL:

abstract{
  abstract a,
  b = abstract{
    abstract x,
    ret = x*10,
  },
  ret = a + b { x = a * 100}.eval.ret
} { a = 5 }.eval.ret

JSON result:

5005

QCL:

# Mutual reference is not allowed
abstract { abstract a, b = a+1 } { a = b+1 } .eval

Error message:

error:
    variable reference "b" does not exist
  |
2 | abstract { abstract a, b = a+1 } { a = b+1 } .eval
  |                                        ^ undefined variable reference


QCL:

# Abstract tuples delay evaluation. They allow but do not require abstract fields.
abstract {
  x = 0,
  y = 1,
  z = 2,
}.eval {
  x = 2,
  y = 4,
}

JSON result:

{"x":2,"y":4,"z":2}

QCL:

abstract {
  x = 2,
  y = 4,
  ret = x * y,
} {
  # Will not work because non-abstract fields in abstract tuples cannot be overridden.
  x += 1,
}.eval

Error message:

error:
    non-abstract field cannot be overridden in an abstract tuple
        (consider first evaluating the abstract tuple into a tuple,
        and then overriding this field)
  |
7 |   x += 1,
  |   ^ override of field here
  |
2 |   x = 2,
  |   ^ previous value defined here
  |
1 | abstract {
  | ^^^^^^^^ tuple marked as abstract here


QCL:

abstract {
  x = 2,
  y = 4,
  ret = x * y,
}.eval {
  # Works.
  x += 1,
}

JSON result:

{"ret":8,"x":3,"y":4}

QCL:

{a=2, b = abstract { abstract c, assert (c%a == 0) }} { b = b { c = 10 }.eval }

JSON result:

{"a":2,"b":{"c":10}}

QCL:

{a=2, b = abstract { abstract c, assert (c%a == 0) }} { b = b { c = 11 }.eval }

Error message:

error:
    assertion failed
  |
1 | {a=2, b = abstract { abstract c, assert (c%a == 0) }} { b = b { c = 11 }.eval }
  |                                          ^^^^^^^^ evaluates to false
  |
1 | {a=2, b = abstract { abstract c, assert (c%a == 0) }} { b = b { c = 11 }.eval }
  |                                            ^ this has value 2
  |
1 | {a=2, b = abstract { abstract c, assert (c%a == 0) }} { b = b { c = 11 }.eval }
  |                                          ^ this has value 11


QCL:

abstract { abstract x, private y = x + x, z = y * y } { x = 10 }.eval

JSON result:

{"x":10,"z":400}

QCL:

abstract { abstract x, private y = x + x } { x = 10 }.eval.y

Error message:

error:
    field marked as private cannot be accessed outside its enclosing tuple
  |
1 | abstract { abstract x, private y = x + x } { x = 10 }.eval.y
  |                                                            ^ access of private field here
  |
1 | abstract { abstract x, private y = x + x } { x = 10 }.eval.y
  |                                ^ defined here
  |
1 | abstract { abstract x, private y = x + x } { x = 10 }.eval.y
  |                        ^^^^^^^ marked as private here


QCL:

# It is possible to mark an overridden field private. It is orthogonal
# to the evaluation order.
abstract {
  abstract x,
  ret = x * x,
} {
  private x = 10,
}.eval

JSON result:

{"ret":100}

QCL:

# It is also possible to mark an overridden field final.
abstract {
  abstract x,
  ret = x * x,
} {
  final x = 10,
}.eval

JSON result:

{"ret":100,"x":10}

QCL:

# This example showcases Church-encoded booleans in an untyped lambda calculus.
{
  private t = abstract { abstract a, abstract b, ret = a },
  private f = abstract { abstract a, abstract b, ret = b },

  # Boolean and
  private and = abstract { abstract p, abstract q, ret = p { a = q, b = p }},
  trueAndFalse  = and { p = t, q = f }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  falseAndTrue  = and { p = f, q = t }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  trueAndTrue   = and { p = t, q = t }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  falseAndFalse = and { p = f, q = f }.eval.ret.eval.ret{a=true,b=false}.eval.ret,

  # Boolean or
  private or = abstract { abstract p, abstract q, ret = p { a = p, b = q }},
  trueOrFalse  = or { p = t, q = f }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  falseOrTrue  = or { p = f, q = t }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  trueOrTrue   = or { p = t, q = t }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
  falseOrFalse = or { p = f, q = f }.eval.ret.eval.ret{a=true,b=false}.eval.ret,
}

JSON result:

{"falseAndFalse":false,"falseAndTrue":false,"falseOrFalse":false,"falseOrTrue":true,"trueAndFalse":false,"trueAndTrue":true,"trueOrFalse":true,"trueOrTrue":true}

QCL:

# The omega combinator. In lambda calculus, the omega combinator diverges (infinite
# loop). But in this language, every abstract tuple evaluation must be explicit.
# Therefore, by placing the eval at the right place, it is possible to see each
# stage of applying the omega combinator.
{
  omega = abstract {
    abstract x,
    ret = x { x = x } # This program would loop if the eval keyword is here.
  },
} {
  omega { x = omega },
  o1 = omega.eval.ret,
  o2 = omega.eval.ret.eval.ret,
  o3 = omega.eval.ret.eval.ret.eval.ret,
  o4 = omega.eval.ret.eval.ret.eval.ret.eval.ret,
  o5 = omega.eval.ret.eval.ret.eval.ret.eval.ret.eval.ret,
  o6 = omega.eval.ret.eval.ret.eval.ret.eval.ret.eval.ret.eval.ret,
  # Ad infitinum.
}

JSON result:

{"o1":null,"o2":null,"o3":null,"o4":null,"o5":null,"o6":null,"omega":null}

QCL:

{
  # This implements a recursive function call using the well-known trick of
  # having an argument to refer to the recursion and passing the function itself.
  factorial = abstract {
    abstract x,
    abstract rec,
    ret = x == 0 && 1 || x * rec { x = x - 1, rec = rec }.eval.ret
  },
} {
  final factorial { rec = factorial }
} {
  f10 = factorial { x = 10 }.eval.ret
}

JSON result:

{"f10":3628800,"factorial":null}

About

An experimental configuration language that produces JSON

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published