I see use experimental 'postderef' being used in Moxie here on line 8. I'm just confused at what it does. The man pages for experimental are pretty vague too,
allow the use of postfix dereferencing expressions, including in interpolating strings
Can anyone show what you would have to do without the pragma, and what the pragma makes easier or possible?
What is it
It's simple. It's syntactic sugar with ups and downs. The pragma is no longer needed as the feature is core in 5.24. But in order for the feature to be supported in between 5.20 and 5.24, it had to be enabled with: use experimental 'postderef'. In the provided example, in Moxie, it's used in one line which has $meta->mro->#*; without it you'd have to write #{$meta->mro}.
Synopsis
These are straight from D Foy's blog, along with Idiomatic Perl for comparison that I've written.
D Foy example Idiomatic Perl
$gimme_a_ref->()->#[0]->%* %{ $gimme_a_ref->()[0] }
$array_ref->#* #{ $array_ref }
get_hashref()->#{ qw(cat dog) } #{ get_hashref() }{ qw(cat dog) }
These examples totally provided by D Foy,
D Foy example Idiomatic Perl
$array_ref->[0][0]->#* #{ $array_ref->[0][0] }
$sub->&* &some_sub
Arguments-for
postderef allows chaining.
postderef_qq makes complex interpolation into scalar strings easier.
Arguments-against
not at all provided by D Foy
Loses sigil significance. Whereas before you knew what the "type" was by looking at the sigil on the left-most side. Now, you don't know until you read the whole chain. This seems to undermine any argument for the sigil, by forcing you to read the whole chain before you know what is expected. Perhaps the days of arguing that sigils are a good design decision are over? But, then again, perl6 is still all about them. Lack of consistency here.
Overloads -> to mean, as type. So now you have $type->[0][1]->#* to mean dereference as $type, and also coerce to type.
Slices do not have an similar syntax on primitives.
my #foo = qw/foo bar baz quz quuz quuuz/;
my $bar = \#foo;
# Idiomatic perl array-slices with inclusive-range slicing
say #$bar[2..4]; # From reference; returns bazquzquuz
say #foo[2..4]; # From primitive; returns bazquzquuz
# Whizbang thing which has exclusive-range slicing
say $bar->#[2,4]; # From reference; returns bazquz
# Nothing.
Sources
Brian D Foy in 2014..
Brian D Foy in 2016..
Related
I'm trying to do a word search with regex and wonder how to type AND for multiple criteria.
For example, how to type the following:
(Start with a) AND (Contains p) AND (Ends with e), such as the word apple?
Input
apple
pineapple
avocado
Code
grep -E "regex expression here" input.txt
Desired output
apple
What should the regex expression be?
In general you can't implement and in a regexp (but you can implement then with .*) but you can in a multi-regexp condition using a tool that supports it.
To address the case of ands, you should have made your example starts with a and includes p and includes l and ends with e with input including alpine so it wasn't trivial to express in a regexp by just putting .*s in between characters but is trivial in a multi-regexp condition:
$ cat file
apple
pineapple
avocado
alpine
Using &&s will find both words regardless of the order of p and l as desired:
$ awk '/^a/ && /p/ && /l/ && /e$/' file
apple
alpine
but, as you can see, you can't just use .*s to implement and:
$ grep '^a.*p.*l.*e$' file
apple
If you had to use a single regexp then you'd have to do something like:
$ grep -E '^a.*(p.*l|l.*p).*e$' file
apple
alpine
two ways you can do it
all that "&&" is same as negating the totality of a bunch of OR's "||", so you can write the reverse of what you want.
at a single bit-level, AND is same as multiplication of the bits, which means, instead of doing all the && if u think it's overly verbose, you can directly "multiply" the patterns together :
awk '/^a/ * /p/ * /e$/'
so by multiplying them, you're doing the same as performing multiple logical ANDs all at once
(but only use the short hand if inputs aren't too gigantic, or when savings from early exit are known to be negligible.
don't think of them as merely regex patterns - it's easier for one to think of anything not inside an action block, what's typically referred to as pattern, as
any combination and collection of items that could be evaluated for a boolean outcome of TRUE or FALSE in the end
e.g. POSIX-compliant expressions that work in the space include
sprintf()
field assignments, etc
(even decrementing NR - if there's such a need)
but not
statements like next, print, printf(),
delete array etc, or any of the loop structures
surprisingly though, getline is directly doable
in the pattern space area (with some wrapper workaround)
I'm trying to see if I can use Red (or Rebol) to implement a simple DSL. I want to compile my DSL to source code for another language, perhaps Red or C# or both - rather than directly interpreting and executing it.
The DSL has only a couple of simple statements, plus an if/else statement.
Statements can be grouped into rules. A rule would get translated into a function definition, with each statement the equivalent statement in the target language.
The parse capability in Red/Rebol is great and lets me implement a parser very easily - in effect it's basically just the definition of the grammar itself.
However I haven't been able to find any examples of how to take the next steps, specifically handling an if statement and translating it to other source code.
Translating an if statement seems a good example of something minimal but still slightly tricky - because in Red having an else means you need to change the if to an either, rather than just an extra optional else.
Traditionally, during parsing I would build an abstract syntax tree, and then have functions to operate on the AST and generate the new source code. Should I be following this same approach or is there some other more idiomatic way in Red ?
I've experimented with using collect/keep in my parse rules to return a block of nested blocks, which in effect forms the AST. Another approach would be to save data into specific objects representing the different statements etc.
I'm still getting to grips with collect/keep, as to when a new block will be created and what will be kept. I'd also like to keep my parser rules as "clean" as possible, with as little other code intertwined in it. So I'm still not sure how best to add in Red code in round brackets in the parse rules. Adding code too early can cause the Red code to get executed, even if the rule eventually fails. Adding code too late means the code may not be executed in the order you expect, especially when dealing with multi-level statements like if, which can contain other statements.
So, specifically, any help on how to translate my example DSL to Red source code would be appreciated. Also any links to implementing DSLs like this in Red or Rebol would be great ! :)
Here are my parse rules :-
Red [
Purpose: example rules for parsing a simple language
]
SimpleLanguageParser: make object! [
Expr: [string! | integer! | block!]
Data: ['Person.AGE | 'Person.INCOME]
WriteMessageToLog: ['write 'message 'to 'log Expr]
SetData: ['set 'data Data '= Expr]
IfStatement: ['if Expr [any Statement] opt ['else [any Statement]] 'endif]
Statement: [WriteMessageToLog | SetData | IfStatement]
Rule: [
'rule word!
[any Statement]
'endrule
]
AnySimpLeLanguage: [Rule | [any Statement]]
]
SL: function [slInput] [
parse slInput SimpleLanguageParser/AnySimpleLanguage
]
An example of some source in the DSL :-
RULE TooYoung
IF [Person.Age < 15]
WRITE MESSAGE TO LOG "too young to earn an income"
SET DATA Person.Income = 0
ELSE
WRITE MESSAGE TO LOG "old enough"
ENDIF
ENDRULE
Translated to Red source code :-
TooYoung: function [] [
either Person.Age < 15 [
WriteMessageToLog "too young to earn an income"
Person.Income: 0
] [
WriteMessageToLog "old enough"
]
]
The data, ie Person.Age, Person.Income, and the function WriteMessageToLog are all things which would have been previously defined.
Note, for simplicity I've left Expr as block! etc, rather than defining Expr in any more detail in the DSL itself. Also, setting Person.Income in the function doesn't work as coded as it sets a local - but that's ok for now :)
Always nice to see someone digging language-oriented programming, keep it up and welcome to Red! ;)
Specifying correct grammar rules is the trickiest part of the job, and you've already nailed that. What's left is to intersperse your PEG (parsing expression grammar) with set, copy, collect/keep combo and paren! expressions in the right places, and then either create an AST from that or, in simplier cases, emit code directly.
Example
Here's a quickly baked (and definitely buggy!) example of how I'd tackled your task. Basically, it's slightly reworked code of yours, where matched patterns are setted, copyed or collected, and then bounded to specific words, which then just pasted into "template" (compose function inside emit-rule) to produce a Red code.
It's not the only way, I believe. #rebolek might come up with more industrial-strength solution, as he has experience with sophisticated parsers, which I'm lacking :P
Followup
As for if/else dilemma, I followed the approach proposed above -- instead of using opt I wrapped rule for else-branch into block and added an alternative match, which just sets false-block to none.
What to use for AST -- anything that allow to express a hierarchical structure, which is either a block! (though for performance gain you might want to use hash! or map!) or an object!. The advantage of object! is that it provides a context to be bound to, but here we're approaching a realm of so-called Bindology ("scoping" rules of Red language), which is another beast :)
Emitting C# code would be harder, but doable -- you'll need to assemble a string instead of Red code. I think, however, that, as a newcomer, you should stick with parsing directly at block-level (the way you done in your example), because it a lot easier and much expressive.
Another interesting (but much hairy) approach would be to re-define all words used in your DSL-block to work as you want.
Resources
We have a wiki entry about Red/Rebol dialects on github, which you might find if not useful, but interesting to read.
Also, two articles (this and this) in Red blog, I think you skimmed over first one already (if not, you should!).
Last, but not least, an exhaustive review of Parse principles and keywords (which has a couple of wrong parts in it though, so, caveat emptor). It's written for Rebol, but you should adapt examples to Red rather easily.
As a relative newcomer to the language, I do agree that there's a lack of examples and tutorials about DSL development, but we're working on that (at least in our heads) :)
Taking 9214's answer as a starting point, I've coded one possible solution. My approach has been :-
try to keep the parse rules as "clean" as possible
use collect and keep to return a block as the result, rather than trying to build a more complex AST
do some minimal translation in the keeps
the resulting block should be valid Red code
which uses predefined functions, where any more complex processing needs to happen
Most simple statements are easily translated to functions eg WRITE MESSAGE TO LOG becomes SL_WriteMessageToLog which can then do whatever it needs to do.
More complicated statements with structure, eg If/Else become functions which take block parameters which can then process the blocks as required.
For the If/Else complication, I've made this into two separate functions, SL_If and SL_Else. SL_If stores the result of the condition in a sequence, and SL_Else checks the latest result and removes it. This allows for nested If/Elses.
The presence of the final endrule can be checked for to ensure the input was correctly parsed. Once this is removed, we should have a valid function definition.
Here's the code :-
Red [
Purpose: example rules for parsing and translating a simple language
]
; some data
Person.AGE: 0
Person.INCOME: 0
; functions to implement some simple SL statements
SL_WriteMessageToLog: function [value] [
print value
]
SL_SetData: function [parmblock] [
field: parmblock/1
value: parmblock/2
if type? value = word! [
value: do value
]
print ["old value" field "=" do field]
set field value
print ["new value" field "=" do field]
]
; hold the If condition results, to be used to determine whether or not to do Else
IfConditionResults: []
SL_If: function [cond stats] [
cond_result: do cond
head insert IfConditionResults cond_result
if cond_result stats
]
SL_Else: function [stats] [
cond_result: first IfConditionResults
remove IfConditionResults
if not cond_result stats
]
; parsing rules
SimpleLanguageParser: make object! [
Expr: [logic! | string! | integer! | block!]
Data: ['Person.AGE | 'Person.INCOME]
WriteMessageToLog: ['write 'message 'to 'log set x Expr keep ('SL_WriteMessageToLog) keep (x)]
SetData: ['set 'data set d Data '= set x Expr keep ('SL_SetData) keep (reduce [d x])]
IfStatement: ['if keep ('SL_If) keep Expr collect [any Statement] opt ['else keep ('SL_Else) collect [any Statement]] 'endif]
Statement: [WriteMessageToLog | SetData | IfStatement]
Rule: [collect [
'rule set fname word! keep (to set-word! fname) keep ('does)
collect [any Statement]
keep 'endrule
]
]
AnySimpLeLanguage: [Rule | [any Statement]]
]
SL: function [slInput] [
parse slInput SimpleLanguageParser/Rule
]
For the example in the original post, the output is :-
TooYoung: does [
SL_If [Person.Age < 15] [
SL_WriteMessageToLog "too young to earn an income"
SL_SetData [Person.Income 0]
]
SL_Else [
SL_WriteMessageToLog "old enough"
]
]
ENDRULE
Thanks for your help to get this far.
Feedback on this approach and solution would be appreciated :)
I'm implementing a PEG parser generator in Python, and I've had success so far, except with the "cut" feature, of which whomever knows Prolog must know about.
The idea is that after a cut (!) symbol has been parsed, then no alternative options should be attempted at the same level.
expre = '(' ! list ')' | atom.
Means that after the ( is seen, the parsing must succeed, or fail without trying the second option.
I'm using Python's (very efficient) exception system to force backtracking, so I tried having a special FailedCut exception that would abort the enclosing choice, but that didn't work.
Any pointers to how this functionality is implemented in other parser generators would be helpful.
Maybe the problem I've had has been lack of locality. The code generated for the left part of the rule would be something like:
cut_seen = False
try:
self.token('(')
cut_seen = True
self.call('list')
self.token(')')
except FailedParse as e:
if cut_seen:
raise FailedCut(e)
raise
Then the code generated for the choice (|) operator will skip the following choices if it catches a FailedCut. What I mean by lack of locality is that the choice catching the FailedCut may be deep up in calls, thus having an effect too-difficult to discern.
Instead of making the code generated for sequences try to inform enclosing choices of cuts, I could make the code generated for choices beware of them. That would make the scope of cuts very local, unlike Prolog's, but good enough for what I want in a PEG parser, which is to commit to an option after a certain token sequence has been seen, so the error reporting is refers to that location in the source, instead of to another location where some other option might have been available.
It just occurred to me that if the code generated for a rule/predicate catches FailedCut and translates it into a normal FailedParse exception, then the cuts will have the right scope.
In reference to #false's question, here's a complete example of what I want to work:
start = expre ;
expre = named | term ;
named = word ':' ! term;
term = word ;
In that grammar, word can be reached through named or term, but I would like the parser to commit to the named branch after it has seen the :.
The Solution
To be fair, I've published my work so far at https://bitbucket.org/apalala/grako/.
In the final solution, sequences are enclosed with this context manager:
#contextmanager
def _sequence(self):
self._push_cut()
try:
yield
except FailedParse as e:
if self._cut():
self.error(e, FailedCut)
else:
raise
finally:
self._pop_cut()
And options in a choice function are enclosed with this:
#contextmanager
def _option(self):
p = self._pos
try:
self._push_ast()
try:
yield
ast = self.ast
finally:
self._pop_ast()
self.ast.update(ast)
except FailedCut as e:
self._goto(p)
raise e.nested
except FailedParse:
self._goto(p)
Which forces an exit out of the choice instead of a return to try the next option.
The cuts themselves are implemented thus:
def _cut(self):
self._cut_stack[-1] = True
The full source code may be found on Bitbucket.
In a Prolog with ISO Prolog's exception handling (catch/3 and throw/1), a cut could be implemented as:
cut. % Simply succeeds
cut :-
throw(cut). % on backtracking throws an exception
This would require to catch that exception at appropriate places. For example, each goal (that is non-terminal) of a user defined predicate could now be wrapped with:
catchcut(Goal) :-
catch(Goal,cut,fail).
This is not the most efficient way to implement cut since it does not free resources upon success of !, but it might be sufficient for your purposes. Also, this method now might interfere with user-defined uses of catch/3. But you probably do not want to emulate the entire Prolog language in any case.
Also, consider to use Prolog's dcg-grammars directly. There is a lot of fine print that is not evident when implementing this in another language.
The solution proposed at the end of my question worked:
cut_seen = False
try:
self.token('(')
cut_seen = True
self.call('list')
self.token(')')
except FailedParse as e:
if cut_seen:
raise FailedCut(e)
raise
Then, any time a choice or optional is evaluated, the code looks like this:
p = self.pos
try:
# code for the expression
except FailedCut:
raise
except FailedParse:
self.goto(p)
Edit
The actual solution required keeping a "cut stack". The source code is int Bitbucket.
Just read it.
I'd suggested a deep cut_seen (like with modifying parser's state) and a save and restore state with local variables. This uses the thread's stack as "cut_seen stack".
But you have another solution, and I'm pretty sure you're fine already.
BTW: nice compiler – it's just the opposite of what I'm doing with pyPEG so I can learn alot ;-)
I've started looking into REBOL, just for fun, and as a fan of programming languages, I really like seeing new ideas and even just alternative syntaxes. REBOL is definitely full of these. One thing I noticed is the use of '/' as the path operator which can be used similarly to the '.' operator in most object-oriented programming languages. I have not programmed in REBOL extensively, just looked at some examples and read some documentation, but it isn't clear to me why there's no ambiguity with the '/' operator.
x: 4
y: 2
result: x/y
In my example, this should be division, but it seems like it could just as easily be the path operator if x were an object or function refinement. How does REBOL handle the ambiguity? Is it just a matter of an overloaded operator and the type system so it doesn't know until runtime? Or is it something I'm missing in the grammar and there really is a difference?
UPDATE Found a good piece of example code:
sp: to-integer (100 * 2 * length? buf) / d/3 / 1024 / 1024
It appears that arithmetic division requires whitespace, while the path operator requires no whitespace. Is that it?
This question deserves an answer from the syntactic point of view. In Rebol, there is no "path operator", in fact. The x/y is a syntactic element called path. As opposed to that the standalone / (delimited by spaces) is not a path, it is a word (which is usually interpreted as the division operator). In Rebol you can examine syntactic elements like this:
length? code: [x/y x / y] ; == 4
type? first code ; == path!
type? second code
, etc.
The code guide says:
White-space is used in general for delimiting (for separating symbols).
This is especially important because words may contain characters such as + and -.
http://www.rebol.com/r3/docs/guide/code-syntax.html
One acquired skill of being a REBOler is to get the hang of inserting whitespace in expressions where other languages usually do not require it :)
Spaces are generally needed in Rebol, but there are exceptions here and there for "special" characters, such as those delimiting series. For instance:
[a b c] is the same as [ a b c ]
(a b c) is the same as ( a b c )
[a b c]def is the same as [a b c] def
Some fairly powerful tools for doing introspection of syntactic elements are type?, quote, and probe. The quote operator prevents the interpreter from giving behavior to things. So if you tried something like:
>> data: [x [y 10]]
>> type? data/x/y
>> probe data/x/y
The "live" nature of the code would dig through the path and give you an integer! of value 10. But if you use quote:
>> data: [x [y 10]]
>> type? quote data/x/y
>> probe quote data/x/y
Then you wind up with a path! whose value is simply data/x/y, it never gets evaluated.
In the internal representation, a PATH! is quite similar to a BLOCK! or a PAREN!. It just has this special distinctive lexical type, which allows it to be treated differently. Although you've noticed that it can behave like a "dot" by picking members out of an object or series, that is only how it is used by the DO dialect. You could invent your own ideas, let's say you make the "russell" command:
russell [
x: 10
y: 20
z: 30
x/y/z
(
print x
print y
print z
)
]
Imagine that in my fanciful example, this outputs 30, 10, 20...because what the russell function does is evaluate its block in such a way that a path is treated as an instruction to shift values. So x/y/z means x=>y, y=>z, and z=>x. Then any code in parentheses is run in the DO dialect. Assignments are treated normally.
When you want to make up a fun new riff on how to express yourself, Rebol takes care of a lot of the grunt work. So for example the parentheses are guaranteed to have matched up to get a paren!. You don't have to go looking for all that yourself, you just build your dialect up from the building blocks of all those different types...and hook into existing behaviors (such as the DO dialect for basics like math and general computation, and the mind-bending PARSE dialect for some rather amazing pattern matching muscle).
But speaking of "all those different types", there's yet another weirdo situation for slash that can create another type:
>> type? quote /foo
This is called a refinement!, and happens when you start a lexical element with a slash. You'll see it used in the DO dialect to call out optional parameter sets to a function. But once again, it's just another symbolic LEGO in the parts box. You can ascribe meaning to it in your own dialects that is completely different...
While I didn't find any written definitive clarification, I did also find that +,-,* and others are valid characters in a word, so clearly it requires a space.
x*y
Is a valid identifier
x * y
Performs multiplication. It looks like the path operator is just another case of this.
This is a stupid question. I've been reading a couple books on F# and can't find anything that explains when you put ;; after a statement, nor can I find a pattern in the reading. When do you end a statement with double semi-colons?
In the non-interactive F# code that's not supposed to be compatible with OCaml, you shouldn't need to ever need double semicolon. In the OCaml compatible mode, you would use it at the end of a top-level function declaration (In the recent versions, you can switch to this mode by using files with .ml extension or by adding #light "off" to the top).
If you're using the command-line fsi.exe tool or F# Interactive in Visual Studio then you'd use ;; to end the current input for F#.
When I'm posting code samples here at StackOverflow (and in the code samples from my book), I use ;; in the listing when I also want to show the result of evaluating the expression in F# interactive:
Listing from F# interactive
> "Hello" + " world!";;
val it : string = "Hello world!"
> 1 + 2;;
val it : int = 3
Standard F# source code
let n = 1 + 2
printf "Hello world!"
Sometimes it is also useful to show the output as part of the listing, so I find this notation quite useful, but I never explained it anywhere, so it's great that you asked!
Are you talking about F# proper or about running F# functions in the F# Interactive? In F# Interactive ;; forces execution of the code just entered. other than that ;; does not have any special meaning that I know of
In F#, the only place ;; is required is to end expressions in the interactive mode.
;; is left over from the transition from OCaml, where in turn it is left over from Caml Light. Originally ;; was used to end top-level "phrases"--that is, let, type, etc. OCaml made ;; optional since the typical module consists of a series of let statements with maybe one statement at the end to call the main function. If you deviate from this pattern, you need to separate the statements with ;;. Unfortunately, in OCaml, when ;; is optional versus required is hard to learn.
However, F# introduces two relevant modifications to OCaml syntax: indentation and do. Top-level statements have to go inside a do block, and indentation is required for blocks, so F# always knows that each top-level statement begin with do and an indent and ends with an outdent. No more ;; required.
Overall, all you need to know is that [O']Caml's syntax sucks, and F# fixes a lot of its problems, but maintains a lot of confusing backward compatibility. (I believe that F# can still compile a lot of OCaml code.)
Note: This answer was based on my experience with OCaml and the link Adam Gent posted (which is unfortunately not very enlightening unless you know OCaml).
Symbol and Operator Reference (F#)
http://msdn.microsoft.com/en-us/library/dd233228(v=VS.100).aspx
Semi Colon:
•Separates expressions (used mostly in verbose syntax).
•Separates elements of a list.
•Separates fields of a record.
Double Semi Colon:
http://www.ffconsultancy.com/products/fsharp_journal/free/introduction.html
Articles in The F#.NET Journal quote F# code as it would appear in an interactive session. Specifically, the interactive session provides a > prompt, requires a double semicolon ;; identifier at the end of a code snippet to force evaluation, and returns the names (if any) and types of resulting definitions and values.
I suspect that you have seen F# code written when #light syntax wasn't enabled by default (#light syntax is on by default for the May 2009 CTP and later ones as well as for Visual Studio 2010) and then ;; means the end of a function declaration.
So what is #light syntax? It comes with the #light declaration:
The #light declaration makes
whitespace significant. Allowing the
developer to omit certain keywords
such as in, ;, ;;, begin, and end.
Here's a code written without #light syntax:
let halfWay a b =
let dif = b - a in
let mid = dif / 2 in
mid + a;;
and becomes with light syntax:
#light
let halfWay a b =
let dif = b - a
let mid = dif / 2
mid + a
As said you can omit the #light declaration now (which should be the case if you're on a recent CTP or Visual Studio 2010).
See also this thread if you want know more on the #light syntax: F# - Should I learn with or without #light?
The double semi-colon is used to mark the end of a block of code that is ready for evaluation in F# interactive when you are typing directly into the interactive session. For example, when using it as a calculator.
This is rarely seen in F# because you typically write code into a script file, highlight it and use ALT+ENTER to have it evaluated, with Visual Studio effectively injecting the ;; at the end for you.
OCaml is the same.
Literature often quotes code written as it would appear if it had been typed into an interactive session because this is a clear way to convey not only the code but also its inferred type. For example:
> [1; 2; 3];;
val it : int list = [1; 2; 3]
This means that you type the expression [1; 2; 3] into the interactive session followed by the ;; denoting the end of a block of code that is ready to be evaluated interactively and the compiler replies with val it : int list = [1; 2; 3] describing that the expression evaluated to a value of the type int list.
The double semicolon most likely comes from OCaml since that is what the language is based on.
See link text
Basically its for historical purposes and you need it for the evaluator (repl) if you use it.
There is no purpose for double semi-colons (outside of F# interactive). The semi-colon, according to MSDN:
Separates expressions (used mostly
in verbose syntax).
Separates
elements of a list.
Separates
fields of a record.
Therefore, in the first instance, ;; would be separating the expression before the first semi-colon from the empty expression after it but before the second semi-colon, and separating that empty expression from whatever came after the second semi-colon (just as in, say C# or C++).
In the instance of the list, I suspect you'd get an error for defining an empty list element.
With regards to the record, I suspect it would be similar to separating expressions, with the empty space between the semi-colons effectively being ignored.
F# interactive executes the entered F# on seeing a double semi-colon.
[Updated to cover F# interactive - courtesy of mfeingold)
The history of the double semicolon can be traced back to the beginnings of ML when semicolons were used as a separator in lists instead of commas. In this ICFP 2010 - Tribute to Robin Milner video around 50:15 Mike Gordon mentions:
There was a talk on F# where someone asked "Why is there double semicolon on the end of F# commands?" The reason is the separator in lists in the original ML is semicolons, so if you wanted a list 1;2;3; and put it on separate lines- if you ended a line with semicolon you were not ending the phrase, so using double semicolon meant the end of the expression. Then in Standard ML the separator for lists became comma, so that meant you could use single semicolons to end lists.