Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411516 Posts in 69380 Topics- by 58436 Members - Latest Member: GlitchyPSI

May 01, 2024, 08:48:13 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)TLScript: unfinished scripting language
Pages: [1]
Print
Author Topic: TLScript: unfinished scripting language  (Read 1040 times)
Triplefox
Level 9
****



View Profile WWW
« on: July 23, 2010, 02:47:49 AM »

The TL in TLScript stands for "Timeline scripting." It's inspired by a variety of systems I've seen(perhaps most notably Tim Sweeney's ZZT-OOP) that let the user execute events over a period of time without being saddled with notions of explicit timers or synchronization. This is my first "serious" stab at language design, after doing some simplistic parsers and Forth-like stuff. I'm at a good point to seek out some feedback, so let me know what you think.

Planned feature list:
  • Designed specifically for embedding in haXe code(since that's my current environment)
  • Mostly user-definable types, casts, and coercions; integer, floating point, and string are included by default, but the goal is to represent an entity component system as a set of types and for domain-specific information of the game to be part of the embedding process, rather than to make a generic "computation" language and add game features on top.
  • Static type inference engine; can determine the missing steps for coercions, greatly simplifying code that starts with one component and then accesses a different one. Heavy annotation is needed for all structures accessed from script; we'll see how good it feels in practice.
  • Conditional branches on returned types, rather than "true or false" values. (This makes it easier to detect and properly handle "null" cases.)
  • Iteration functions.
  • Multiple methods of timeline usage:
  • "@time event" to specify a specific time,
  • "event / event / event..." or "event /time event /time event /time..." to perform events at regular intervals,
  • ">> tweenvar {code}" and "<< tweenvar" to specify begin and endpoints for code that runs repeatedly over a given time period(presumably using the tween variable as it goes from 0. to 1.)
  • ">><< tweenvar time {code}" to do a tween with a specified period(the other format is rewritten to this)
  • Goto in time and to labels("%label"). Labels can be zapped ("^label"), a carryover from ZZT-OOP which lets you disable and reenable goto statements to the label, letting you control state/flow behavior without introducing new named variables.
  • Latches; similar to tweens, but they are called indefinitely until you hit a stop point. You can also specify the intervals where they're called.

Syntax Examples
Code:
/* basic function calls */
@0 print("hello world")
   var tempvar : Number
   tempvar := add(1 2)
   print(tempvar)

Code:
/* A sequence of events */
@0 firstevent
@4 secondevent
@8 thirdevent
@10 goto(0)
/* no parens necessary to call functions */

Code:
/* A sequence of events using time incrementing */
@0 north /1 north / east / wait / say("hello")

Code:
/* A 10-second tween to bounce an element */
var this : Entity
@0 >> bouncein {setY(this mult(bouncein 20))}
@10 << bouncein

Code:
/* A latch that locks the entity to the player position at 0, 5, and 10 seconds */
var this : Entity
@0 (< lockon 5 {setXY(this getXY(player()))}
@10 >) lockon

Code:
/* A common use of labels; shot is implicitly called by external events; alternatively a latch could be used to monitor variables and trigger the goto. */
@0 say("Argh I am evil boss man!")
@1 attackPlayer /1 moveTowardsPlayer / attackPlayer / goto(1)
@5 %shot %shot %shot %shot
   zap("shot")
   say("ARGH YOU SHOT ME!")
   goto(1)
@6 %shot
   say("YOU SHOT ME FIVE TIMES, I AM DEAD!")
   die() 

Code:
/* A branch. */
var this : Entity
@0 findPickup?(): pickup
   Pickup: {
      walkTowards(this pickup)
   }
   None: {
      wander(this)
   }

Code:
/* An iteration. It moves the particles one by one. (I think this may not be good enough, actually.) */
@0 particles!(): particle
   {
      moveX(particle 1.3)
      moveY(particle 1.1)
   }

Right now I have the type inference engine working, and earlier tonight I finished my first pass at the parser using PEG.js. You can play with the parser here, just copy in my grammar and the examples:

http://pegjs.majda.cz/online

It's still rough around the edges. The comment syntax is breaking on me, and probably the strings do too. It doesn't help that PEG.js gives no information about failures deep in the recursion, it just fails it at the top level. I expect major changes before I have a final thingy going.

Peg.js Grammar:
Code:
/*

PEG.js grammar

to do:
test

*/

start
  = (outerstatement)*


identifier "Identifier"
  = strdata:([a-z] / [A-Z])+ {return strdata.join("")}

dotid
  = id:identifier "." {return id}

nsidentifier "Namespace Identifier"
  = rest:(dotid)* last:identifier {return {"type":"nsid","data":rest.concat(last)}}

ws "Whitespace"
  = ([\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20])* {return "";}

outerstatement
  = body:(vardecl / timestamp / codestatement) {return body;}
 
vardecl "Variable Declaration"
  = ws "var" ws namedef:nsidentifier ws ":" ws typedef:identifier ws {return {"type":"var","data":{"name":namedef,"type":typedef}};}

codeblock "Code Block"
  = ws "{" ws body:(codestatement)* ws "}" ws {return {"type":"codeblock","data":body}}

int "Integer"
  = ws strdata:[0-9]+ ws {return {"type":"int","data":parseInt(strdata.join(""))}}

float "Float"
  = data : (floatfull / floatfirst / floatlast) {return {"type":"float","data":parseFloat(data)}}

floatfull
  = ws first:[0-9]+ "." last:[0-9]+ ws {return first.join("")+"."+last.join("");}

floatfirst
  = ws first:[0-9]+ "." ws {return first.join("")+".";}

floatlast
  = ws "." last:[0-9]+ ws {return "."+last.join("");}

number "Number"
  = float / int 

singlequote
  = ws ['] body:[^']* ['] ws {return body.join("");}

doublequote
  = ws ["] body:[^"]* ["] ws {return body.join("");}

triplequote
  = ws '"""' body:(!'"""' .)* '"""' ws {result = []; for (n in body) result.push(body[n][1]); return result.join("");}

string "String"
  = body:(triplequote / singlequote / doublequote) {return {"type":"string","data":body}} 

timestamp "Timestamp"
  = ws "@" ws time:(number) ws {return {"type":"timestamp","data":time};}

argdata "Argument"
  = arg:(funcall/string/number/nsidentifier) ws {return arg;}

args "Arguments"
  = [(] ws argdata:(argdata)* [)] {return argdata;}

funcall "Function"
  = ws fcall:identifier fargs:args ws {return {"type":"function","data":{"fcall":fcall,"args":fargs}}}

assignment "Assignment"
  = ws assignee:nsidentifier ws ":=" ws fcall:identifier fargs:args ws {return {"type":"assignment","data":{"assignee":assignee,"fcall":fcall,"args":fargs}}}

iterator "Iterator"
  = ws fcall:identifier [!] fargs:args ws ":" ws ivar:identifier ws block:codeblock {return {"type":"iterator","data":{"fcall":fcall,"ivar":ivar,"args":fargs,"block":block}}}

branch "Branch"
  = ws fcall:identifier [?] fargs:args ws ":" ws bvar:identifier blocks:(branchblock)* ws {return {"type":"branch","data":{"fcall":fcall,"args":fargs,"branchvar":bvar,"blocks":blocks}};}

branchblock "Branch Block"
  = ws btype:identifier ws ":" ws bcode:codeblock {return {"type":btype,"code":bcode}}

tweenstart "Tween start"
  = ws ">>" ws tweenvar:identifier ws code:codeblock ws {return {"type":"tweenstart","data":{"var":tweenvar,"code":code}}}


tweenend "Tween end"
  = ws "<<" ws tweenvar:identifier ws {return {"type":"tweenend","data":tweenvar}}

tweencomplete "Complete tween"
  = ws ">><<" ws tweenvar:identifier ws time:number ws code:codeblock ws {return {"type":"tweencomplete","data":{"var":tweenvar,"time":time,"code":code}}}

tweenoffunction "Tween of function"
  = ws ">><<" ws tweenvar:identifier ws fcall:funcall ws code:codeblock ws {return {"type":"tweenoffunction","data":{"var":tweenvar,"fcall":fcall,"code":code}}}

codestatement
  = comment/funcall/assignment/iterator/branch/tweenstart/tweenend/tweencomplete/tweenoffunction/label/zapped/latchstart/latchend/ptimeadvance/timeadvance/nakedfuncall

comment "Comment"
  = "/*" body:(!'*/' .)* "*/" {result = []; for (n in body) result.push(body[n][1]); return result.join("");}

label "Label"
  = ws "%" ws name:identifier ws {return {"type":"label","data":name};}

zapped "Zapped"
  = ws "^" ws name:identifier ws {return {"type":"zapped","data":name};}

latchstart "Latch start"
  = ws "(<" ws latchvar:identifier ws interval:number ws code:codeblock ws {return {"type":"latchstart","data":{"var":latchvar,"interval":interval,"code":code}}}

latchend "Latch end"
  = ws ">)" ws latchvar:identifier ws {return {"type":"latchend","data":latchvar}}

timeadvance "Time advance"
  = ws "/" ws {return {"type":"timeadvance","data":{"type":"int","data":0}};}

ptimeadvance "Parameterized Time Advance"
  = ws "/" tval:number ws {return {"type":"timeadvance","data":tval};}

nakedfuncall
  = ws fcall:identifier {return {"type":"function","data":{"fcall":fcall,"args":[]}}}

Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #1 on: July 23, 2010, 11:50:03 AM »

I like the idea of using a identifier for both the tween variable and delimiting the tween. It seems economical.

However,the timing aspect seems somewhat limited. All timing is specified in terms of frames, not seconds, or relative time. Everything must fit on one timeline, no matter how disjoint events are meant to be (e.g. what is the significance of @5 and @6 in your shot example).

FunctionName():Foo seems to specify Foo as the name of an argument. That's crazy, elsewhere, :Foo would mean "of type Foo" (which fits better with what other languages do. Again, angle braces are the opposite way round for latches and tweens, which by the way, I cannot see what the difference is.
Logged
Triplefox
Level 9
****



View Profile WWW
« Reply #2 on: July 23, 2010, 01:07:17 PM »

Many of those are just unclarity on my part.

Timing allows floating point. And the language isn't necessarily using any specific time unit; we could be referring to frames, seconds, turns, "story markers" or any other sequenced system you can think of. Relative time, as well, is just a matter of when the script is launched, and there are myriad possibilities in the embedding process for ways to control how things are launched, updated and terminated.

The shot example actually doesn't need @6. I wasn't thinking correctly when I wrote that and figured "one state resulting from this event is getting shot but not dying, the other state is getting shot and dying, let's move them into different time periods." It would probably need @6 if I included a "play anim" thingy(then again, maybe not - it might just add a tween over everything else).

With regards to the function call syntax, there are three things going on here. First of all, there are no function definitions in the program body(that's done in embedding), hence I reuse the type annotation syntax in a different way.

"function!():foo" gives you a named value for iterators. I guess I could do "for function!() in name" or somesuch, but it would break the sigil-heavy thing I have going.

"function?():foo" gives you a named value for conditional results. Recall how in an Algol-type language, you'll say:

Code:
if (result())
{
   ...
}

But then result()'s output disappears. I'm preserving it because I expect to have a lot of branches of this type:

Code:
/* destroy the monsters */

aiIs?(entity) : ai
Player: {}
Pickup: {}
Monster: {
   kill(ai)
}

I don't like the latch syntax yet. I'm iffy about the tween syntax. They are similar but different. Latches are indefinite repetitions over time - they get called either every time you step the script, or on some interval of time. This, as well as the way tweens work, is important because, as I said, the nature of time is undefined. You could be incrementing frames, or just fractions of a second. And since you can loop or go backwards in time, sometimes you want a certain thing to repeat until an event occurs to stop it.

Code:
@0 (< latcha 2 {pulse("red")}
@1 (< latchb 2 {pulse("green")}
@2 goto(1.9)
%somethinghappened
>) latcha >) latchb

In this example I've set up two latches that can do "flip-flop" behavior(like pulsing a light red and then green) in the background, freeing up execution for other things until %somethinghappened is called, at which point the latches immediately terminate. With a tween this can't be done because the tween needs to know where it ends.
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic