hey what's going on everyone my name is
Tyler and welcome to the channel today
we're going to be implementing
environments in our custom programming
language so without further Ado let's
just dive on in so I'm going to go ahead
and create a new environment.ts file
inside of our runtime folder and so what
we're going to want to do is define a
class that's basically going to hold the
structure for a scope AKA an environment
so I'm going to do exports defaults
class and we're just going to call it
environments like so
now an environment class is going to
have a few important things it's going
to need to keep track of a possible
parent right you can imagine the global
scope will have its own environments and
the children will have their own
environments but they still need to
reference to the parent so I'm going to
do parent is equal to a environments
like so however you notice this question
mark which if you're not familiar with
typescript this means that this could be
undefined so there might be a parent or
there might not so we're going to Define
it like that then we're going to want to
have a way to actually get the variables
that are defined in said environments so
I'm going to call this privence and then
parents like so and here we're going to
call this variables
um and this is going to be a map type
a map of strings two and we want the
runtime
runtime values like so so let me go
ahead and actually Define our runtime
values type
so I'm going to import
runtime vowel from dot slash not
interpreter slash dot slash values.ts
and there we go and the reason we're
getting an error is because there's no
initializer so let's go ahead and Define
a Constructor which will take in a
possible appearance
um
and we'll say the parents environment is
going to be
um we'll pass in in environments like so
and what we'll do is we'll just do this
stop parents is equal to parents
environment then we need to do this dot
variables is equal to a new map like so
and there we go now we have the place
where we can store our link to our
parents and the variables that are
defined in the skip which the string
will be the identifier and the value
that will be held in runtime will be
stored there now in this video we want
to implement the functions for this so
let's define a few functions first we're
going to want a few public methods one's
going to be declare
variable which is going to take in the
VAR name
which is going to be a string obviously
and the value which is going to be a
runtime value like so and we're also
going to return a runtime value as well
now to declare a variable it's pretty
simple but we still want to verify that
the variable is not already defined in
our current scope so what we're going to
do is we're just going to do if
this Dot variables.house
our VAR name
then all we're going to do is we're just
going to throw
um
cannot
declare
variable and I'm also going to use this
as a template string
and we're going to pass in the VAR name
of our name
as it's already
like so okay so we Define the variable
if it's not already defined then we can
go ahead and Define it by just doing
this dot variables dot sets if you pass
the new bar name
and the value and then we can just
return
value so that way this function always
returns a value and there we go we've
already implemented how we can declare a
variable that's not already defined
let's now Implement a function that
assigns to a variable that has already
been declared so public
assign bar it's going to take in
for our name
and the value
and it's also going to return
the value that we implemented okay so
when we're assigning a variable it's
also very very simple we want to
basically set the variable but we also
want to make sure that it exists so what
we're going to do is we are going to
make the executive decision that if a
variable does not exist kind of like the
opposite of here then we're going to
throw an error however you could make it
that if the variable name is not found
then instead of throwing what you could
do is you could just assign it but in my
decision I want this to be more explicit
for a language so we're going to say if
this stop variables
dots
oh wait wait there is one thing here my
bad we are going to create a function
called
um resolve
in this resolve function is going to
take in the VAR name
in return in environments what this is
going to do is allow us to Traverse the
scope of environments to find a variable
if we need to assign right you can
imagine if we're trying to assign x and
x doesn't exist here but X is just in
the global scope we want to Traverse
through the parents and get that
reference to the variables so what we're
going to do is inside of our resolve
function we're first going to check if
our current environment has this Rando
if dot variables dot has
VAR name then all we're going to do is
we're just going to return this that's
going to return the environments that
we're currently in then we're going to
want to check if this stop parents
is undefined what we're going to want to
do is throw an error and we're going to
say cannot
cannot resolve
and so we're going to pass in the
variable name
I also need to do template string
cannot resolve VAR name I'm going to put
this in quotes
um as it does not exist
there we go and then if we make it this
far then what we're going to do is we're
just going to return
um this dot parents dot resolve and
we're going to pass in the bar name like
so now what this is going to do is it's
going to check if our current scope has
it if it doesn't and we don't have a and
we have a parent it's going to go to the
parent scope and repeat the process so
we don't have to worry if we end up if
the parent scope doesn't have it or the
global scope at some point doesn't have
that it's going to throw an error and
that's what we're going to do so when we
assign a VAR we can do const e and V
so we want to get the environment that
the variable might be defined in and
what we're going to do is EMV equals
this dot resolve
our name and then what we're going to do
is we're just going to do EMV Dot
variables.sets
for our name is equal to value
and then we're going to return
the value like so so now we have the
ability to assign a variable declare a
variable resolve a variable scope right
and now we're just going to implement
one last function which is simply given
a string we'll return the value of this
variable so we're going to do public
look up
bar and it's going to take in
bar name which is a string in its return
type is going to be a runtime
value like so we're just going to do
const e and V is equal to and we're
going to do this dot resolve because we
want to get the location
of our variable name and then we're just
going to do or turn e and v ah not even
b e and V Dot uh
variables dot gets and we're going to
pass in
burn them now the reason this is going
to throw in air is because typescript
has no clue that this might not be
undefined we know it's not going to be
undefined if we pass in to resolve this
variable name and we get this value back
without throwing an error then we know
for a fact that this is going to return
the environment that this exists in so
when we do dot get we know it's never
going to return undefined so what we're
going to do is we're just going to do as
runtime value like so and there we go
now we have our four main methods for
interacting with an environment and
these are pretty much the only methods
we're going to need for the most the
majority of our interpreter for the
environment so let's minimize these
and so let's actually make sure this is
exported it is and now we can actually
use this inside of our
um inside of our interpreter so I'm
going to come in here and to begin when
we evaluate a program we want to know
the current environment we're currently
in so I'm going to create an environment
variable and say it's of type
environment and whenever we pass
anything in to evaluate we're also going
to be passing in the environments that's
therefore going to have access to the
current variables so whenever we pass
whenever we call evaluate we'll need to
pass in an environment
and in here let me look at all the
places that don't have the CMV and pass
it in so we're going to pass an EMV
and EnV now you notice EMV is not
defined in these places so the good rule
of thumb is every single scope of an
eval function is going to have this
environment passed in like so so we're
going to pass it in right here
Envy
and then up here we're going to want to
press in the environments we're going to
declare the environments
as well as the numeric binary expression
for now we won't need to pass in an
environment since we know there's no
need for us to evaluate anything right
the left hand side and right hand side
have both been evaluated to their lowest
forms so we don't need an environment
there however we need one here
like so okay so now we passed in an
environment to all of our functions that
call eval except our main right we need
our main function to now pass in an
environment so I'm going to pass in an
environment like so
and so we're going to create this
environment up at the very top level
um up here so we're going to do cons EMV
is equal to in New environment and
that's going to work just fine however
we maybe want to pass in global
variables right so the Way languages
would want to define global variables is
you'll want to define a custom set of
variables in your definement right so
custom set of identifiers and such so
let's now go ahead and actually
Implement a
default parameter that we could pass in
to pass in
more variables right so we can see that
we could do environment Dot and we have
access to declare ver let's say we want
to declare a variable called X and we
want the type to be a number of a
hundred so let's do value
100
and the type is number now you'll notice
this is going to yell at me
right and numb for
my God
number
oh yeah like so there we go now we've
defined a variable X in our environment
just like that so the last thing we want
to be able to do to actually make it so
we can access X and work with them is
inside of our interpreter we're going to
want to handle
numeric literals we handle no literals
we handle but we also are going to want
to handle the identifiers we're going to
have case
identifier and what we're going to do is
we're going to return a eval
in identifier like that and we're going
to pass in the AST node as an identifier
and then we're going to pass in the
current environment
and now let's actually go ahead and
implement this eval identifier function
right here so we're going to do export
and not export sorry function
eval identifier is going to take in an
idents
which is going to be of type identifier
in an environment of type environments
the return type is a runtime value and
what we're going to do is we're just
going to Simply do constant Val is equal
to we're going to get access to the
currently passed in environment
dots and we're going to look up the
variable of our name so ident dot symbol
which is going to be the string that the
identifier has from our AST
and then we're just going to return well
like so and there we go now we actually
have all the tools we need to be able to
identify and work with variables so to
demonstrate this since we don't have the
ability to assign variables until the
next step said for now let's try to get
access to this x which we know is the
value of a hundred and then after we do
that we're going to clean up this right
here so that way this can look a little
bit cleaner so let's actually give this
a shot let's open up our terminal
clear it out into denim
run Dash a and main.ts if we run the
rebel and type in X we would expect to
get a value of a hundred and sure enough
we do we're able to get a value of a
hundred if we pass an x minus 500
divided by 3 we should expect to get x
minus 250 and there we go right so this
is seems to be working X plus 500 gives
600 x divided by 2 is 50 and X Plus null
is well no
so this seems to work let's also try
accessing a variable that doesn't exist
like Foo and we can see uncut cannot
resolve Foo as it doesn't exist which it
doesn't now this looks really ugly in my
opinion and I would like to have some
kind of macros I'm going to call them
because I'm from c a macro kind of
function that will simply Define these
types for us so we're going to call it
make num
number and we're going to pass in a
hundred like so and then I won't need to
do this so then we can when we declare a
variable for testing we can do this so
let's create a make number function
inside of our values.ts so here we have
a type of null and number I'm actually
going to change the way the null type
works so it just holds a null value the
literal null and for number we're going
to do the same thing I'm going to create
a function we're going to export it
make
and it's going to take in a number and
if we don't pass one in we'll default to
zero like so
so what we're going to do is we're just
going to return a type
of a number
value of n as number value like so and
then I'm going to copy this same
function for make novel so it's going to
be called make
no
like so it's going to take in a type of
null return a value of null and this is
going to be
a null value and it doesn't need to take
in anything okay so now let's fix the
main.ts
um sorry The Interpreter which has all
these values of null we need to change
from instead of being a string
to well an actually thing or even better
we could just call return
return make null
everywhere we need to so return
make null
and up top we'll also call return make
null
there we go that simplifies a little bit
of our code and now we can get rid of
this null value reference which is quite
nice
in the main.ts we're going to want to
import our make number function
like so and there we go now we have the
ability to create a number and such
however one thing we also want to be
able to do is environments.declare
things like true
to being something like a make bull
as well as a false variable
and we also might want to also have null
just be its own custom type right so
instead of having null be a value that
keeps constantly getting returns maybe
what we actually want to do
is instead just return a
um
we actually just want to return a null
value instead so let's actually go ahead
and implement this so to implement the
true and the false
we're going to have to create
our own custom make Bull type so let's
declare it and make
bull
like so cool that looks beautiful now
let's go ahead and implement it in our
values.ts file I'm going to create a new
type up here called Boolean
and what we're going to do is we're
going to basically just copy this right
here
and instead of null value this is going
to be a booval
and we'll call it a Boolean vowel this
is going to be of type
Boolean and the value is going to be of
type Boolean there we go we're going to
have a make
Bool function which is going to take in
a type of Boolean the value is going to
be B which B is going to be equal to
True by default however you could pass
in something else and as a Boolean value
like so
and there we go now we have the ability
to create true or false values inside of
the runtime now there's one last thing
that's kind of ugly here and that's that
inside of the environment or not
environments sorry The Interpreter we
can start looking at where we start
running the code and we have a null
literal type which I don't actually
quite like very much instead I would
rather us just have null be handled as
if it's an identifier just like true and
false currently are so let's actually
get rid of this type right here
and what we actually want to do is get
rid of every single reference basically
to a null type in our front end so in
the AST we're going to get rid of this
null literal like so which means we also
have to get rid of it down here
and then inside of the parser we're
going to get rid of all of the
references to null literal as well so
that means we're going to go down to the
primary expression
at the very bottom and get rid of the
null literal here as well
and that means in the lexor we no longer
need this null type as well currently
literal types there's only going to be
one other we'll have to add and that'll
be
a string literal which we're not going
to add right now but this will basically
be a string in quotation marks but for
now that's actually everything we needed
to add we can get rid of this right here
like so and now if you wanted to try out
this code it'll actually work the exact
same as before except we'll have three
new types we'll have a null type so we
can type null and we still get the same
results as before it has a type of null
we can type true has a type of Boolean
false has a type of false and we can do
operations on them so we can do X Plus
45 and we get 145 we can also do x minus
true and we would expect to get null
because you cannot do binary operations
with a value like this so that's really
cool now we have a very very basic
environment but we have all the tools we
need to now really start pumping out
things with our language in the next
episode we're going to add variable
declaration so it'll allow us to write
code like this let's x equal 45
as well as let Foo equals no something
like this and then we'll actually be
able to assign them using x equals 45
plus Foo which we would expect two
results and null so I hope to see you
guys in the next video Until then I'll
talk to you later bye