code examples

This is a moderated forum that collects tutorials, guides, and references for creating Transcendence extensions and scripts.
User avatar
Periculi
Fleet Officer
Fleet Officer
Posts: 1282
Joined: Sat Oct 13, 2007 7:48 pm
Location: Necroposting in a forum near you

I appreciate what you guys are doing, and have found the conversation about code helpful. Thanks!
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:1. Run from innermost function to outermost function.
Perhaps this is a disagreement in conceptual approach to the problem of programming...

But, let's try to explore those differences. When you say 'run from innermost to outermost function', what are you really saying?

In (add 2 (multiply 3 3)), what gets evaluated first, what is innermost and what is outermost function?

I'm guessing that the innermost function here would be 'multiply' and the outermost function would be 'add'. So your algorithm would claim that multiply gets run first, and then the add function.

BUT, that is simply INCORRECT. The number '2' definitely gets evaluated BEFORE the innermost function, namely 'multiply'. In this example it doesn't actually matter, because '2' is an immutable number. But if someone would take your algorithm literally, it would lead him to false conclusions in many complicated examples, where arguments of outer functions are something that requires more complicated evaluations themselves.

The parser just fetches tokens as they appear, and evaluates them unless the function which is being processed explicitly forbids immediate evaluation (like lambda). It only evaluates the 'innermost' function when it actually encounters it in left to right order; it doesn't 'skip' previous arguments to first evaluate innermost functions, as your algorithm would suggest. In the previous example it does mean that the multiplication will occur BEFORE addition - there is no other way of doing it, because 'add' function simply doesn't have both arguments before multiply is executed - but it doesn't mean that left to right order is violated. The arguments will be evaluated in the order they are given, no matter whether they belong to innermost or outermost function. Namely, 2 will be evaluated before 3 and 3, as the example in my previous post demonstrates. Ask George if you don't trust me. :wink:

And why is it that you think, that 'left to right' is unclear or unnatural even in examples like (add 2 (multiply 3 3))?

What is hard to understand in this? You work it out left to right:
1. we see add function. Therefore, we need to grab 2 args and add them
2. we see '2'. Great, this will be our first argument to add
3. oops, we see a parenthesis. Ok, lets evaluate that stuff inside the parentheses to yield our 2nd argument for addition
3a. Ok, I see multiply function. So it means I need 2 args to multiply
3b and 3c. I got a number 3 two times, so let's multiply them. It's 9.
4. ok, so 9 is our second argument, let's finally perform the addition. The result is 11.

Now this algorithm above is both 100% correct and 100% left to right (granted, addition is performed 'last' while the func 'add' comes first. BUT, we did know from the BEGINNING that we are going to add 2 numbers. So addition is already accounted for, even if its execution had to be delayed until we get both args. So I think it can rightly be called left to right).
Why complicate things?
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

This is indeed a good conversation. And thanks to Betelgeuse for getting this kind of documentation started.

I don't think I have much to add to the discussion.

Lisp-like languages are weird because both functions and control structures are represented in the same way.

For example:

Code: Select all

(add (divide 10 5) (multiply 2 5))
The arguments to (add) are evaluated left-to-right recursively. That is, first we evaluate the result of (divide 10 5) and then we evaluate the result of (multiply 2 5). If necessary, we recurse and evaluate the parameters of (divide) and (multiply). [I think the word 'recursive' is what Betelgeuse is trying to capture in his 'innermost to outermost' comment.]

Note that there is no ambiguity about precedence. In C-like languages, an expression like:

Code: Select all

10 / 5 + 2 * 5
is ambiguous unless you know that the compiler treats * and / as having higher precedence than +

In Lisp-like languages, there is no ambiguity (and no need for precedence).

Evaluation gets a little tricky with control structures:

Code: Select all

(if (eq (add 2 2) 5) (dbgOutput "2 + 2 = 5"))
Notice that the syntax for (if) is the same as the syntax for (add). Both take two parameters. But in this case, the second parameter (dbgOutput) never gets evaluated--because the test in the if-statement is false.

How about this function:

Code: Select all

(for i 1 10 (dbgOutput "cool!"))
How many times does the last parameter get evaluated?

So you see, parameter evaluation in Lisp-like languages depend on the function being called.

Sometimes the parameters are evaluated consistently, left-to-right recursively (as with the add function). Sometimes parameters are not evaluated at all. Sometimes parameters are evaluated more than once.

Note: Right now it is not possible to create a user-defined function that controls parameter evaluation. But in the future I will add that capability. That would allow people to create their own control structures.
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:now that I think about it some more I think you are right the left to right rule should be first (they both would have different outcomes in some code but I am too lazy to test right now). But you really need to explain more on the parens. They are scary to non coders.

ps: I blame this whole argument on lack of sleep
Yes, the parentheses really are a nightmare, easily agreed on that. :twisted:

They bother me too, even though I understand their purpose. But without the 'highlight the corresponding bracket' feature of good editors, I'd be lost, or I would need several failed runs to get things right.

I believe the best way to help people understand the parentheses is to give them the simplest possible examples of various functions, to show them what they can look like without parentheses; and then add an example where they do appear.

Parentheses, I'd say (even though George might correct me on this in certain cases) have twofold purpose: first, anything INSIDE them is basically treated like a pattern (func [arg]^). In other words, the first token after an open parenthesis is a function name (and all other tokens till the closing parenthesis are the arguments to that function). That function will be invoked as soon as the parenthesis are encountered in left to right evaluation, and the arguments which follow that function are evaluated and prepared. The other purpose is that everything inside the parentheses is regarded as a single argument to the outer function, in whose argument list the parentheses occur.

The exceptions I could think of are variable lists in functions 'block' and 'lambda'. The first argument for both block and lambda is a list of variables, in parentheses (or Nil). Normally the first name in those lists would be treated like a function, but after block or lambda it gets treated the special way. I'd be curious what this parser really looks like inside. :wink:

Other than that, there is nothing special about them; they just come so often because the syntax requires them. How else would you group things together? Some languages use different sort of brackets for different sort of grouping; C has all 3 variants: braces {}, brackets [] and parentheses (). This script language of Transcendence uses just one version, probably emulating lisp.

Basically, anything that isn't a simple number, or string, or variable name, must be inside parentheses. Including the block function, which needs to be surrounded by them too.

(if 1 2)
(if 1 (block Nil 1 2 3 4))

This outlines the syntax of both if and block. The first example yields 2, the second yields 4. The 'if' function evaluates the first arg, if it's 'true' (non-Nil) then the 2nd arg is evaluated. If the first arg is Nil, however, the if function skips the 2nd arg and yields Nil, unless the third arg is also supplied (the 'else' arg) in which case the function yields that 3rd arg (i.e. whatever that arg returns).

In the second example, the 2nd arg to the if function is the 'block' function placed inside parentheses (so that the entire stuff inside parentheses is regarded, by the if function, as a single argument, just like the number '2' in the first example). It should be obvious that both pairs of parentheses adhere to the pattern (func arg arg ...). And how the block function works; it first expects a list of local variable names (those variables will be forgotten as soon as the block function finishes) or Nil if we don't need local variables. Then, it expects any number of args which are just evaluated one after the another, left to right. Of course, this example does nothing, the block function just evaluates the numbers 1, 2, 3, 4 and does nothing with them - except that it returns the last one (and it's not just arbitary decided, it's very purposeful that the very last one is returned).
In some real example, those args 1 2 3 4 wouldn't just be simple numbers, but other functions, inside their own pairs of parentheses. Those functions might be simple, like
(objSendMessage gPlayerShip Nil "blah blah") but they could also be very complex, like an if function which in turn invokes another 'block' function - and thus increasing the number of parentheses greatly; like in (again the simplified example so the syntax rules are obvious):

(if 1 (block Nil 1 2 (if 3 (block Nil 4 5 6) ) 7 8 ))

It could be rewritten as

Code: Select all

(if 1
    (block Nil
        1
        2
        (if 3
            (block Nil
                4
                5
                6
            )
        )
        7
        8
    )
)
I hope this can be recognized as the skeleton of many real examples in various .xml files. It's basically the same thing, just that in real examples there are various useful functions instead of numbers 1,2,3,4,5,6,7,8. For example, instead of 3, there could be (objIsContaminated gPlayerShip), which would evaluate as 'true' (non-Nil) if our ship was contaminated. Then the if function would then cause the block that follows to be executed, or skipped it if we weren't contaminated.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Lets make it more confusing. :wink:

Say you wanted to make a list of stuff and it happened to start with add? What do you do? You do not want it to be taken as a function whether or not you have the correct number of arguments for it. The little ' saves the day. You can put ' in front of any opening parentheses and the stuff inside will be treated like a list.

Here are some examples.

Code: Select all

'(add 1 1)
simple example that gives you the list (add 1 1)

Code: Select all

'(add lambda and a block)
A more interesting example because if you didn't have the ' you would get some interesting errors.

Code: Select all

'(add (add 2 2) (add 3 3))
Even in this kind of example no add will be evaluated. Even the (add 3 3) it waits until that matching closing parenthesis.
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

all this talk about various things and we missed the obvious.

List
A list is a set of ordered elements.

Element
A element is a list, a string, a number, or a variable.

All functions are in the form of lists. A list starts at the opening parentheses and goes to the closing parentheses.

Here are some examples of lists.

Code: Select all

'("a" "b" 23 (add 1 1))

Code: Select all

'(((((a)))))
This is an interesting example. A few comments. The list (a) is not the same as a. The list that contains the list (a) is not the same as the list (a). Lists can contain other lists easily allowing for many more complex structures to be created.

Code: Select all

'()
This a special kind of list. As you can see it is empty. This is what Nil is. Nil is not zero Nil is an empty list.
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

A string is a ordered set of characters. It is usually defined by putting double quotes around it.

Code: Select all

"This is a string"
There are some characters that need special treatment due to being hard to type or other considerations. Here is a list of them and what they do. (please tell me if I am missing any)

\n puts in a newline, kind of like the enter key
\" puts in a " without ending the string
\\ puts in a \ character

Code: Select all

"This \"string\" is \n a bit more complex"
ps: the \n doesn't work with dbgOutput but does work in other cases :P
Crying is not a proper retort!
User avatar
Ttech
Fleet Admiral
Fleet Admiral
Posts: 2767
Joined: Tue Nov 06, 2007 12:03 am
Location: Traveling in the TARDIS
Contact:

You've been busy
Image
Image
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Numbers

A number in transcendence is a whole number in the range -2147183648 to 2147183647.

Any numbers between those two including those two numbers can be used in transcendence.

Because that is so simple lets do some more fun stuff with numbers.

Numbers in transcendence (well most programming languages) wrap around. So if you add one to the greatest number you will get the least number. So (add 2147183647 1) will return the number -2147183648. Small disclaimer I don't know if overflow (programmer speak for what happened) is considered an error for the purposes of isError and errBlock.

So what do you do if you you want something bigger than those numbers or you want a decimal? Then you just treat the numbers if they where already multiplied by a number (an easy example is think of percents). You will loose some numbers but that is what you have too trade off.

here some examples

Say you want at least 3 places after the decimal you would then multiply everything by 1000 before you even code.

15.346 would become 15346 that way you don't have to worry about Transcendence not having decimals you can do without.

So how would you display a number that was pre multiplied?

(cat (divide number 1000) "." (modulus number 1000))
that will give a string of your number in the correct format.

now for a different example say you want bigger numbers?
You would just drop off the last 3 digits and say it was divided by 1000.

So 15566723 would become 15566. As you can see you did lose some information (you did lose storage room in the other example but those numbers are the high numbers here you lose the low numbers).

This one is even easier to display
(cat number "000")
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

A item criteria in Transcendence is a specially formatted string for filtering items. The first part is a list of characters representing the general categories you want to be true.

* all categories
a armor
d device (weapon, shield, drive, etc.)
f fuel
l launcher weapon only
m missile
r reactor
s shields
t miscellaneous
u useful (armor coating, ROM, etc.)
v drive
w weapon (including launchers)
I is installed
D is damaged
N is not damaged
S is useable
U is not installed

Like this "D" means the damaged items.

If you want to say not you put ~.
Like this "~r" means all items that are not reactors"

When you put more than one it is treated as an and between them.
"D~r" means items that are damaged items and not reactors.

Modifiers: a modifier is something that is declared by the items you are filtering individually. You can even make up your own modifiers for items and it will still be able to filter by that.
Modifiers are formatted like this. A space followed by a + (it must have this modifier) or - (it can not have this modifier) followed by the modifier followed by a ;
There are two special modifiers that are a little different than the standard ones. they are lt (must be less than level) and gt (must be greater than level. These are the followed by the number of the level. Like this " lt;7" means less than level 7. Does anyone know what the other parts of these two modifiers? like =# and =$ and =

some examples are

"w <8" means weapons less than level 8
"* -Illegal; -Military; -Alien; -ID; -CannotOrder; -NotForSale; <10" means all items that do not have the modifiers illegal, military, alien, id, cannotorder, notforsale, and must be less than level 10.
Last edited by Betelgeuse on Sat Dec 15, 2007 11:52 am, edited 1 time in total.
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Note: Right now it is not possible to create a user-defined function that controls parameter evaluation. But in the future I will add that capability. That would allow people to create their own control structures.
hmm some things that I would like to create with that

A variant of objEnumItems that did itemStructs instead of itemlistpointers. (is there a way to get the itemlistpointer from the itemStruct quickly?)

(enumStations criteria maxdistance variable function)
(enumShips criteria maxdistance variable function)
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

code style.

In Transcendence script (and generally most programming in general) making your code readable is vital for debugging and allowing people to read it. I will talk about three parts of this here.

Comments
To make a comment in code just prefix a line with ; and Transcendence will ignore it. They are very helpful in longer code saying what you are doing now and give an obvious clue on what your intentions where when you wrote this code.

variable naming
You can name you variables anything you want in Transcendence but it is always good to name them something that gives some clue to there meaning.

spacing
Two parts to this one tabs and newlines. The idea with tabs is you want everything inside of something tabbed over one more than what it is inside. This is very helpful in finding what code is being effected by what code without looking through to find the ending parenthesis.
Newlines are needed because no one wants to scroll left or right. Transcendence allows you to put a newline where ever you have a space other than in strings or comments (a newline ends a comment).

Making your code look nice will help you find any errors, allow you to come back later if you need to change it in some way, and will make it easier to get help (no one will help you if the code is unreadable.)


Lets have a example of code. First the bad version can you tell what it is doing?

Code: Select all

(block (a b c) (setq b True) (switch (geq (sysGetLevel) 7) (setq c 0) (geq (sysGetLevel) 3) (setq c (subtract 360 (multiply 72 (subtract (sysGetLevel) 2)))) (setq c 360)) (enum (sysFindObject gSource "Ts") a (switch (and (objIsEnemy gSource a) (gr (add (objGetDestiny a) 1) c)) (setq b Nil) (objHasAttribute a "uncharted") (setq b Nil) (objSetKnown a))) (if b	(objSendMessage gSource Nil "Mapped all stations in system") (objSendMessage gSource Nil "Mapped charted stations in system")) (itmSetKnown gItem) (objRemoveItem gSource gItem 1))
Now think of that one just one line (the board auto-wraps)

Lets compare that with the original and see how much more readable it is.

Code: Select all

		(block (obj mappedAll mapEnemy)
			(setq mappedAll True)
			
			; Compute the probability of mapping an enemy station
			(switch
				(geq (sysGetLevel) 7)
					(setq mapEnemy 0)
					
				(geq (sysGetLevel) 3)
					(setq mapEnemy (subtract 360 (multiply 72 (subtract (sysGetLevel) 2))))
					
				(setq mapEnemy 360)
				)
			
			; Iterate over all stations in the system
			(enum (sysFindObject gSource "Ts") obj
				(switch
					(and (objIsEnemy gSource obj) (gr (add (objGetDestiny obj) 1) mapEnemy))
						(setq mappedAll Nil)
					
					(objHasAttribute obj "uncharted")
						(setq mappedAll Nil)
						
					(objSetKnown obj)
					)
				)

			(if mappedAll
				(objSendMessage gSource Nil "Mapped all stations in system")
				(objSendMessage gSource Nil "Mapped charted stations in system")
				)

			; Identify the item
			(itmSetKnown gItem)

			; Remove ROM
			(objRemoveItem gSource gItem 1)
			)
Even if you didn't know about the system map rom you can tell that is what the second code with the variable naming, tabs, and comments.
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

flag
A flag is a number that represents a set of true or false things. A number can hold 32 true or false things.

Flags are best represented in binary and you need to be wary of messing with them.

Here are some helper functions if you need to mess with them (George does things manually so don't work off his things unless you know what you are doing)

Code: Select all

(setq twoToThe (lambda (power)
	(item '(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 2147483648 4294967296) power)
	))
	
(setq getFlag (lambda (flag getSpot)
	(modulus (divide flag (twoToThe getSpot)) 2)
	))	
	
(setq clearFlag (lambda (flag clearSpot)
	(if (eq 1 (getFlag flag clearSpot))
		;clear the spot
		(subtract flag (twoToThe clearSpot))
		;flag is already clear so do nothing
		flag
		)
	))
	
(setq setFlag (lambda (flag setSpot)
	(if (eq 0 (getFlag flag setSpot))
		;set the spot
		(add flag (twoToThe setSpot))
		;flag is already set so do nothing
		flag
		)
	))
Crying is not a proper retort!
User avatar
Ttech
Fleet Admiral
Fleet Admiral
Posts: 2767
Joined: Tue Nov 06, 2007 12:03 am
Location: Traveling in the TARDIS
Contact:

Betelgeuse wrote:flag
A flag is a number that represents a set of true or false things. A number can hold 32 true or false things.

Flags are best represented in binary and you need to be wary of messing with them.

Here are some helper functions if you need to mess with them (George does things manually so don't work off his things unless you know what you are doing)

Code: Select all

(setq twoToThe (lambda (power)
	(item '(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 1073741824 2147483648 4294967296) power)
	))
	
(setq getFlag (lambda (flag getSpot)
	(modulus (divide flag (twoToThe getSpot)) 2)
	))	
	
(setq clearFlag (lambda (flag clearSpot)
	(if (eq 1 (getFlag flag clearSpot))
		;clear the spot
		(subtract flag (twoToThe clearSpot))
		;flag is already clear so do nothing
		flag
		)
	))
	
(setq setFlag (lambda (flag setSpot)
	(if (eq 0 (getFlag flag setSpot))
		;set the spot
		(add flag (twoToThe setSpot))
		;flag is already set so do nothing
		flag
		)
	))
that is complicated. :/
Image
Image
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

as I said in the post you need to be wary when working with flags. If you have any questions about anything though I would gladly hear them. I would love feedback on the various posts in here.
Crying is not a proper retort!
Post Reply