My own DSL language

i’m designing a custom dsl to possibly use in my games for things like item definitions.

# kraskaska's dsl language
# inspired by HashiCorp Configuration Language
# this language is not implemented, and currently a mere concept
hero "mike" {
    defense = 99 # variable
    attack = 88
    _self => . # dot means this context ("mike"); "_" doesn't mean to code anything but might be useful for variables that are not used by dsl structure but by user; => means set by reference, unlike just = which will copy value
    skills { # this is a function that will produce an array from object with names.
        "devil's lick" {
            damage = _self/attack / 2 # accessing properties is like directory structure, so this is basically hero/"mike"/attack
            heal {
                target => _self
                amount = _self/attack / 2 # some math is supported
            }
        }
    }
}
clone_hero "mike" "jane" # this is a function invocation.
# hero "mike" is also a function invocation. but it also can just be a regular object
# if we say it is a function, then dsl consumer can define it to be reduced to following JSON
# "heroes": {
#   "mike": {/* rest of the hero */}
# }
# which simply makes hero part to heroes, and optionally allows multiple heroes to be added the same way, depending on what the consumer wants
# if it is an object, then it will reduce to this
# "hero": {
#   "mike": {/* rest of the hero */}
# }
# nothing has changed, but now you can't have multiple heroes as key "hero" already defined.....
# editor note: soon my head will start to hurt

# you can modify existing object, provided that your consumer supports it
heroes/"mike"/defense = 77

# you can also alter multiple properties at the same time if that's an object
heroes/"mike" <=> {
  defense = 22
  attack = 11
}
# ... or an array
heroes/"mike"/skills <=> {
  {
    name = "50/50" # we are outside of dsl, skills handholded us by comverting "name" {} to array-appropriate data
    damage = luck { # function
        "50%" = 0
        "50%" = 100
    }
  }, # arrays use commas
}
# in the case of array it will join items together
# if you want to join/alter without assignment, use <>

resulting json (you could also consume this dsl as is, nobody is forcing you to use json, in fact it probably will be better to not use it):

{
  "heroes": {
    "mike": {
      "defense": 22,
      "attack": 11,
      "skills": [
        {
          "name":"devil's lick",
          "damage": 44, // It was evaluated before attack was changed. you could make it lazy and evaluate on access, or you can assign by reference so that it will evaluate each time (not in json tho)
          "heal": {
            "target": "myself", // You can write custom exporter to make things like this possible. or don't be a freak and consume dsl raw
            "amount": 44
          }
        },
        {
          "name": "50/50",
          "damage": {
            "%luck%": { // again, custom exporter
              "50%": 0,
              "50%2": 100 // json doesn't like duplicate kekes
            }
          }
        }
      ]
    },
    "jane": { // Jane was cloned
      "defense": 99,
      "attack": 88,
      "skills": [
        {
          "name":"devil's lick",
          "damage": 44,
          "heal": {
            "target": "myself",
            "amount": 44
          }
        }
      ]
    }
  }
}
# another example
# covers other topics

# functions

hero = function 2 { # they are first class, woo!
  if (heroes is null) { # if statements, they are also functions
    heroes = {}
    context (heroes/*) { # contexts, for things like skills in first example. think some sort of metatables in lua
      skills = function 1 {
        . = { // self destruct in that hero on evaluation
          for ({ name, value } in (function_parameter 0)) { # function can inheretly be macros. also, see that array? those are tokens, they are like strings but follow rules of variable naming
            (value <> {
              name = ../name
            }),
          }
        }
      }
    }
  }
  heroes <=> {
    "${function_parameter 0}" (function_parameter 1)  # string substitution. you can also enclouse function invokation in braces to use it in an argument
  }
}
pass_by_reference = function 1 {
  reference = reference_parameter 0 # (same as reference => *referenced variable*, used for lazy evaluation below vvv) 
}

# if statements
foo = 3
bar = if (foo is more than 5) ("baz") ("zab")
# syntax: if (condition) (true) (false)
# so nested statement is like so:
if (false) (
  3
) (if (false) (
  2
) (if (true) (
  1
) (
  0
)))
# it evaluates to 1

# lazy evaluation
bir = 3
bur $= bir * 2 # uses $= instead of normal assignment operator (it means time is money)
bir = 4
print bur # bur only now evaluated to 8.
# if you want to delay it even further
pass_by_reference bur # (bur is not yet evaluated because we passed a reference)

# dynamic evaluation
bur => bir * 3 # uses the same syntax as set by reference, which fits perfectly for what it does
print bur # 12
bir = 5
print bur # 15
bir = 6
print bur # 18

# tokens
# in case you are too lazy to type quotes or you need variable-like feel. has less priority so you don't always get tokens.
array_of_tokens = {token1, token2, token3}
# if token3 is set to 3, it would have been {token1, token2, 3}

# loops
for (range 5) { # first parameter expects array
  print "yay" # yay\nyay\nyay\nyay\nyay\n
}
for (i in (range 5)) { # suffix function, about them later. for loop also allows you to pass in current variable. i happens to be a token
  print i # 1\n2\n3\n4\n5\n
}
# suffixed functions
in = 1 suffixed_function 1 { // funnily enough function here is also suffixed
  {
    pass = function_parameter -1 # prefixed parameters start with -1
    iterable = function_parameter 0
  }
}

# i'm pretty sure this is not evaluatable in json

i will update this as i think of more sugar.

This is pretty crazy! Awesome work :slight_smile: